├── LICENSE ├── README.md ├── combined ├── README.md ├── flutter_dart.md ├── flutter_dart__under_6K.md ├── flutter_dart_bloc_mocktail.md ├── flutter_dart_bloc_mocktail__under_6K.md ├── flutter_dart_change_notifier.md ├── flutter_dart_change_notifier__under_6K.md ├── flutter_dart_provider.md ├── flutter_dart_provider__under_6K.md ├── flutter_dart_riverpod_mockito.md ├── flutter_dart_riverpod_mockito__under_6K.md ├── flutter_with_bloc.md ├── flutter_with_bloc__under_6K.md ├── flutter_with_riverpod.md └── flutter_with_riverpod__under_6K.md ├── media ├── flutter_ai_rules.png ├── mocktail_md_01.png └── mocktail_md_02.png └── rules ├── bloc.md ├── code_review.md ├── dart_3_updates.md ├── effective_dart.md ├── firebase ├── cloud_firestore.md ├── cloud_functions.md ├── firebase_analytics.md ├── firebase_app_check.md ├── firebase_auth.md ├── firebase_crashlytics.md ├── firebase_database.md ├── firebase_in_app_messaging.md ├── firebase_messaging.md ├── firebase_remote_config.md └── flutterfire_configure.md ├── flutter_app_architecture.md ├── flutter_change_notifier.md ├── flutter_errors.md ├── mockito.md ├── mocktail.md ├── provider.md └── riverpod.md /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Ivanna 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flutter Rules for Windsurf, Cursor, and Other AI-Powered IDEs 2 | 3 | Flutter Rules for Windsurf, Cursor 4 | 5 | ## ⚡ TLDR 6 | 7 | If you want to use `.cursor/rules` or `.windsurfrules`, just copy the contents of the rule set of your choice (e.g., `combined/flutter_dart__under_6K.md`) into your IDE’s global or local rules. 8 | For maximum control, you can also copy the `/rules` folder into your project and reference rules as needed (e.g., "Read @rules/firebase/ and set up a project with Realtime Database, App Check, and Analytics."). 9 | 10 | ## 🚀 Introduction 11 | 12 | This repository provides a comprehensive, (almost) non-opinionated collection of Flutter-related rules tailored for use with **Windsurf**, **Cursor**, and other AI-powered IDEs. These rules are designed to improve your development workflow, ensure consistency, and help you get the most out of your AI coding assistant. 13 | 14 | ## 📁 Repository Structure 15 | 16 | - **`rules/`** 17 | Contains individual rule files, each focused on a specific topic or tool (e.g., `bloc.md`, `effective_dart.md`, etc.). 18 | These files are: 19 | - Based **only** on official documentation from Flutter, Dart, or relevant package websites. 20 | - Categorized by subject to make them easy to mix, match, and reference. 21 | - Meant to be refined, adjusted, or extracted based on your project needs. 22 | 23 | - **`combined/`** 24 | Contains pre-made, curated sets of rules that combine commonly used topics (e.g., Flutter + Riverpod + Mockito). 25 | These files: 26 | - Are kept under **6,000 characters** to comply with **Windsurf's** limit. 27 | - Can be used **as-is** by copying them into your global or local rules configuration. 28 | 29 | ## ✅ How To Use 30 | 31 | ### Option 1: Use Pre-Made Combined Rules 32 | 33 | If you want a quick setup: 34 | 35 | 1. Browse the [`combined/`](./combined) folder. 36 | 2. Copy a file that suits your project. 37 | 3. Paste it into your IDE's global or local rules config. 38 | 4. You're ready to go. 39 | 40 | ### Option 2: Use Individual Rule Files 41 | 42 | If you prefer more control: 43 | 44 | 1. Browse the [`rules/`](./rules) folder. 45 | 2. Pick files relevant to your project (e.g., `riverpod.md`, `bloc.md`, etc.). 46 | 3. You can: 47 | - **Include** them directly in your IDE setup. 48 | - **Reference** them in prompts to add context. 49 | - **Extract** only the parts that are useful for your context. 50 | - **Include** them partially or fully in a PRD (Product Requirements Document). 51 | 52 |
53 | Example usage with Mocktail rules 54 | Example usage with Mocktail rules 55 |
56 | 57 | Everything is modular — use what works best for you. 58 | 59 | ### Option 3: Download All Rules via CLI 60 | 61 | You can fetch the latest rules directly into your project with a single command: 62 | 63 | ```sh 64 | git clone --depth 1 https://github.com/evanca/flutter-ai-rules.git temp_repo && mkdir -p docs && cp -r temp_repo/rules/* docs && rm -rf temp_repo 65 | ``` 66 | 67 | This will copy all rules into a `docs` folder in your project. After all rules are in the `docs` folder, you can reference them individually based on your needs—without the limitations of IDE ruleset length. Simply use them as context where applicable. For example: 68 | 69 | “Read `@docs/bloc.md` and create test coverage for new methods.” 70 | 71 | **Pro tip:** 72 | You can also add a global rule that references this `docs` folder. For example, in your global rules or settings, you might write: 73 | “We have a `/docs` folder containing various rules based on Flutter and Dart documentation.” 74 | 75 | ## 📏 No Opinions, Just Documentation 76 | 77 | All rules are sourced from official documentation — no personal preferences or subjective interpretations. That’s intentional. You’re free to alter them to your taste, but this repo keeps things objective by sticking to the source. 78 | 79 | Note: This might sometimes lead to contradictory rules (e.g., if one package suggests one folder architecture and another recommends a different one). 80 | 81 | ## 📌 Use Cases 82 | 83 | - Set up global rules for a Flutter project in your IDE. 84 | - Configure project-specific constraints for popular state management packages. 85 | - Provide clear expectations in a PDR when working with a team. 86 | - Extract only what you need to avoid rule clutter. 87 | 88 | ## 🛠️ Contributing 89 | 90 | Contributions are welcome! If you'd like to suggest a new rule or improve an existing one, here’s how you can help: 91 | 92 | 1. Fork this repository. 93 | 2. Add or modify rules in the appropriate folder. 94 | 3. Submit a pull request with a clear explanation of your changes. 95 | **Make sure to include an official documentation link** for any rule set you’re adding or modifying to keep everything objective and reliable. 96 | 97 | ## 📚 References 98 | 99 | Here are the official sources that have been used to build these rules: 100 | 101 | ### Flutter 102 | - [Flutter App Architecture](https://docs.flutter.dev/app-architecture) - Official Flutter architecture guidelines 103 | - [Flutter Common Errors](https://docs.flutter.dev/testing/common-errors) - Common errors documentation 104 | - [Flutter ChangeNotifier State Management](https://docs.flutter.dev/data-and-backend/state-mgmt/simple) - Simple state management with ChangeNotifier 105 | 106 | ### Dart 107 | - [Effective Dart](https://dart.dev/effective-dart) - Official Dart style guidelines 108 | - [Dart 3 Updates](https://dart.dev/language) - Documentation on Dart 3 features including: 109 | - [Records](https://dart.dev/language/records) 110 | - [Patterns](https://dart.dev/language/patterns) 111 | - [Pattern Types](https://dart.dev/language/pattern-types) 112 | - [Branches](https://dart.dev/language/branches) 113 | 114 | ### State Management 115 | - [Bloc Library](https://bloclibrary.dev/) - Official Bloc library documentation 116 | - [Provider](https://pub.dev/packages/provider) - Official Provider package documentation 117 | - [Riverpod](https://riverpod.dev/) - Official Riverpod documentation 118 | 119 | ### Testing 120 | - [Mockito](https://pub.dev/packages/mockito) - Official Mockito for Dart documentation 121 | - [Mocktail](https://pub.dev/packages/mocktail) - Official Mocktail documentation 122 | 123 | ### Firebase 124 | - [Firebase for Flutter](https://firebase.google.com/docs/flutter/setup) - Official Firebase Flutter documentation 125 | - [Code with Andrea](https://codewithandrea.com/articles/flutter-firebase-multiple-flavors-flutterfire-cli/) - How to Setup Flutter & Firebase with Multiple Flavors using the FlutterFire CLI 126 | -------------------------------------------------------------------------------- /combined/README.md: -------------------------------------------------------------------------------- 1 | # Combined Rules Character Count 2 | 3 | This document contains character counts for all combined rule files. 4 | 5 | | Trimmed Rules | Character Count | 6 | |--------------|-----------------| 7 | | flutter_dart__under_6K.md | 5858 | 8 | | flutter_dart_bloc_mocktail__under_6K.md | 4887 | 9 | | flutter_dart_change_notifier__under_6K.md | 5553 | 10 | | flutter_dart_provider__under_6K.md | 5681 | 11 | | flutter_dart_riverpod_mockito__under_6K.md | 5635 | 12 | | flutter_with_bloc__under_6K.md | 5145 | 13 | | flutter_with_riverpod__under_6K.md | 5779 | 14 | 15 | | Full Rules | Character Count | 16 | |-----------|-----------------| 17 | | flutter_dart.md | 14400 | 18 | | flutter_dart_bloc_mocktail.md | 24789 | 19 | | flutter_dart_change_notifier.md | 17155 | 20 | | flutter_dart_provider.md | 17264 | 21 | | flutter_dart_riverpod_mockito.md | 31917 | 22 | | flutter_with_bloc.md | 22250 | 23 | | flutter_with_riverpod.md | 30379 | 24 | 25 | --- 26 | 27 | **Windsurf limits:** `global_rules.md` and `.windsurfrules` are limited to 6000 characters each. Any content above 6000 characters will be truncated and Cascade will not be aware of them. 28 | Source: [Windsurf Documentation - Memories](https://docs.windsurf.com/windsurf/memories), accessed Apr 15 2025. 29 | 30 | **Cursor limits:** Keep rules concise. Under 500 lines is a good target. Source: [Cursor Documentation - Rules](https://docs.cursor.com/context/rules#best-practices), accessed May 7 2025. -------------------------------------------------------------------------------- /combined/flutter_dart.md: -------------------------------------------------------------------------------- 1 | # Effective Dart Rules 2 | 3 | ### Naming Conventions 4 | 1. Use terms consistently throughout your code. 5 | 2. Follow existing mnemonic conventions when naming type parameters (e.g., `E` for element, `K`/`V` for key/value, `T`/`S`/`U` for generic types). 6 | 3. Name types using `UpperCamelCase` (classes, enums, typedefs, type parameters). 7 | 4. Name extensions using `UpperCamelCase`. 8 | 5. Name packages, directories, and source files using `lowercase_with_underscores`. 9 | 6. Name import prefixes using `lowercase_with_underscores`. 10 | 7. Name other identifiers using `lowerCamelCase` (variables, parameters, named parameters). 11 | 8. Capitalize acronyms and abbreviations longer than two letters like words. 12 | 9. Avoid abbreviations unless the abbreviation is more common than the unabbreviated term. 13 | 10. Prefer putting the most descriptive noun last in names. 14 | 11. Consider making code read like a sentence when designing APIs. 15 | 12. Prefer a noun phrase for non-boolean properties or variables. 16 | 13. Prefer a non-imperative verb phrase for boolean properties or variables. 17 | 14. Prefer the positive form for boolean property and variable names. 18 | 15. Consider omitting the verb for named boolean parameters. 19 | 16. Use camelCase for variable and function names. 20 | 17. Use PascalCase for class names. 21 | 18. Use snake_case for file names. 22 | 23 | ### Types and Functions 24 | 1. Use class modifiers to control if your class can be extended or used as an interface. 25 | 2. Type annotate variables without initializers. 26 | 3. Type annotate fields and top-level variables if the type isn't obvious. 27 | 4. Annotate return types on function declarations. 28 | 5. Annotate parameter types on function declarations. 29 | 6. Write type arguments on generic invocations that aren't inferred. 30 | 7. Annotate with `dynamic` instead of letting inference fail. 31 | 8. Use `Future` as the return type of asynchronous members that do not produce values. 32 | 9. Use getters for operations that conceptually access properties. 33 | 10. Use setters for operations that conceptually change properties. 34 | 11. Use a function declaration to bind a function to a name. 35 | 12. Use inclusive start and exclusive end parameters to accept a range. 36 | 37 | ### Style 38 | 1. Format your code using `dart format`. 39 | 2. Use curly braces for all flow control statements. 40 | 3. Prefer `final` over `var` when variable values won't change. 41 | 4. Use `const` for compile-time constants. 42 | 43 | ### Imports & Files 44 | 1. Don't import libraries inside the `src` directory of another package. 45 | 2. Don't allow import paths to reach into or out of `lib`. 46 | 3. Prefer relative import paths within a package. 47 | 4. Don't use `/lib/` or `../` in import paths. 48 | 5. Consider writing a library-level doc comment for library files. 49 | 50 | ### Structure 51 | 1. Keep files focused on a single responsibility. 52 | 2. Limit file length to maintain readability. 53 | 3. Group related functionality together. 54 | 4. Prefer making fields and top-level variables `final`. 55 | 5. Consider making your constructor `const` if the class supports it. 56 | 6. Prefer making declarations private. 57 | 58 | ### Usage 59 | 1. Use strings in `part of` directives. 60 | 2. Use adjacent strings to concatenate string literals. 61 | 3. Use collection literals when possible. 62 | 4. Use `whereType()` to filter a collection by type. 63 | 5. Test for `Future` when disambiguating a `FutureOr` whose type argument could be `Object`. 64 | 6. Follow a consistent rule for `var` and `final` on local variables. 65 | 7. Initialize fields at their declaration when possible. 66 | 8. Use initializing formals when possible. 67 | 9. Use `;` instead of `{}` for empty constructor bodies. 68 | 10. Use `rethrow` to rethrow a caught exception. 69 | 11. Override `hashCode` if you override `==`. 70 | 12. Make your `==` operator obey the mathematical rules of equality. 71 | 72 | ### Documentation 73 | 1. Format comments like sentences. 74 | 2. Use `///` doc comments to document members and types; don't use block comments for documentation. 75 | 3. Prefer writing doc comments for public APIs. 76 | 4. Consider writing doc comments for private APIs. 77 | 5. Consider including explanations of terminology, links, and references in library-level docs. 78 | 6. Start doc comments with a single-sentence summary. 79 | 7. Separate the first sentence of a doc comment into its own paragraph. 80 | 8. Use square brackets in doc comments to refer to in-scope identifiers. 81 | 9. Use prose to explain parameters, return values, and exceptions. 82 | 10. Put doc comments before metadata annotations. 83 | 11. Document why code exists or how it should be used, not just what it does. 84 | 85 | ### Testing 86 | 1. Write unit tests for business logic. 87 | 2. Write widget tests for UI components. 88 | 3. Aim for good test coverage. 89 | 90 | ### Widgets 91 | 1. Extract reusable widgets into separate components. 92 | 2. Use `StatelessWidget` when possible. 93 | 3. Keep build methods simple and focused. 94 | 95 | ### State Management 96 | 1. Choose appropriate state management based on complexity. 97 | 2. Avoid unnecessary `StatefulWidget`s. 98 | 3. Keep state as local as possible. 99 | 100 | ### Performance 101 | 1. Use `const` constructors when possible. 102 | 2. Avoid expensive operations in build methods. 103 | 3. Implement pagination for large lists. 104 | 105 | # Dart 3 Updates 106 | 107 | ### Branches 108 | 1. Use `if` statements for conditional branching. The condition must evaluate to a boolean. 109 | 2. `if` statements support optional `else` and `else if` clauses for multiple branches. 110 | 3. Use `if-case` statements to match and destructure a value against a single pattern. Example: `if (pair case [int x, int y]) { ... }` 111 | 4. If the pattern in an `if-case` matches, variables defined in the pattern are in scope for that branch. 112 | 5. If the pattern does not match in an `if-case`, control flows to the `else` branch if present. 113 | 6. Use `switch` statements to match a value against multiple patterns (cases). Each `case` can use any kind of pattern. 114 | 7. When a value matches a `case` pattern in a `switch` statement, the case body executes and control jumps to the end of the switch. `break` is not required. 115 | 8. You can end a non-empty `case` clause with `continue`, `throw`, or `return`. 116 | 9. Use `default` or `_` in a `switch` statement to handle unmatched values. 117 | 10. Empty `case` clauses fall through to the next case. Use `break` to prevent fallthrough. 118 | 11. Use `continue` with a label for non-sequential fallthrough between cases. 119 | 12. Use logical-or patterns (e.g., `case a || b`) to share a body or guard between cases. 120 | 13. Use `switch` expressions to produce a value based on matching cases. Syntax differs from statements: omit `case`, use `=>` for bodies, and separate cases with commas. 121 | 14. In `switch` expressions, the default case must use `_` (not `default`). 122 | 15. Dart checks for exhaustiveness in `switch` statements and expressions, reporting a compile-time error if not all possible values are handled. 123 | 16. To ensure exhaustiveness, use a default (`default` or `_`) case, or switch over enums or sealed types. 124 | 17. Use the `sealed` modifier on a class to enable exhaustiveness checking when switching over its subtypes. 125 | 18. Add a guard clause to a `case` using `when` to further constrain when a case matches. Example: `case pattern when condition:` 126 | 19. Guard clauses can be used in `if-case`, `switch` statements, and `switch` expressions. The guard is evaluated after pattern matching. 127 | 20. If a guard clause evaluates to false, execution proceeds to the next case (does not exit the switch). 128 | 129 | ### Patterns 130 | 1. Patterns are a syntactic category that represent the shape of values for matching and destructuring. 131 | 2. Pattern matching checks if a value has a certain shape, constant, equality, or type. 132 | 3. Pattern destructuring allows extracting parts of a matched value and binding them to variables. 133 | 4. Patterns can be nested, using subpatterns (outer/inner patterns) for recursive matching and destructuring. 134 | 5. Use wildcard patterns (`_`) to ignore parts of a matched value; use rest elements in list patterns to ignore remaining elements. 135 | 6. Patterns can be used in: 136 | - Local variable declarations and assignments 137 | - For and for-in loops 138 | - If-case and switch-case statements 139 | - Control flow in collection literals 140 | 7. Pattern variable declarations start with `var` or `final` and bind new variables from the matched value. Example: `var (a, [b, c]) = ('str', [1, 2]);` 141 | 8. Pattern variable assignments destructure a value and assign to existing variables. Example: `(b, a) = (a, b); // swap values` 142 | 9. Every case clause in `switch` and `if-case` contains a pattern. Any kind of pattern can be used in a case. 143 | 10. Case patterns are refutable; if the pattern doesn't match, execution continues to the next case. 144 | 11. Destructured values in a case become local variables scoped to the case body. 145 | 12. Use logical-or patterns (e.g., `case a || b`) to match multiple alternatives in a single case. 146 | 13. Use logical-or patterns with guards (`when`) to share a body or guard between cases. 147 | 14. Guard clauses (`when`) evaluate a condition after matching; if false, execution proceeds to the next case. 148 | 15. Patterns can be used in for and for-in loops to destructure collection elements (e.g., destructuring `MapEntry` in map iteration). 149 | 16. Object patterns match named object types and destructure their data using getters. Example: `var Foo(:one, :two) = myFoo;` 150 | 8. Constant patterns match if the value is equal to a constant (number, string, bool, named constant, const constructor, const collection, etc.). Use parentheses and `const` for complex expressions. 151 | 9. Variable patterns (`var name`, `final Type name`) bind new variables to matched/destructured values. Typed variable patterns only match if the value has the declared type. 152 | 10. Identifier patterns (`foo`, `_`) act as variable or constant patterns depending on context. `_` always acts as a wildcard and matches/discards any value. 153 | 11. Parenthesized patterns (`(subpattern)`) control pattern precedence and grouping, similar to expressions. 154 | 12. List patterns (`[subpattern1, subpattern2]`) match lists and destructure elements by position. The pattern length must match the list unless a rest element is used. 155 | 13. Rest elements (`...`, `...rest`) in list patterns match arbitrary-length lists or collect unmatched elements into a new list. 156 | 14. Map patterns (`{"key": subpattern}`) match maps and destructure by key. Only specified keys are matched; missing keys throw a `StateError`. 157 | 15. Record patterns (`(subpattern1, subpattern2)`, `(x: subpattern1, y: subpattern2)`) match records by shape and destructure positional/named fields. Field names can be omitted if inferred from variable or identifier patterns. 158 | 16. Object patterns (`ClassName(field1: subpattern1, field2: subpattern2)`) match objects by type and destructure using getters. Extra fields in the object are ignored. 159 | 17. Wildcard patterns (`_`, `Type _`) match any value without binding. Useful for ignoring values or type-checking without binding. 160 | 18. All pattern types can be nested and combined for expressive and precise matching and destructuring. 161 | 162 | ### Records 163 | 1. Records are anonymous, immutable, aggregate types that bundle multiple objects into a single value. 164 | 2. Records are fixed-sized, heterogeneous, and strongly typed. Each field can have a different type. 165 | 3. Records are real values: store them in variables, nest them, pass to/from functions, and use in lists, maps, and sets. 166 | 4. Record expressions use parentheses with comma-delimited positional and/or named fields, e.g. `('first', a: 2, b: true, 'last')`. 167 | 5. Record type annotations use parentheses with comma-delimited types. Named fields use curly braces: `({int a, bool b})`. 168 | 6. The names of named fields are part of the record's type (shape). Records with different named field names have different types. 169 | 7. Positional field names in type annotations are for documentation only and do not affect the record's type. 170 | 8. Record fields are accessed via built-in getters: positional fields as `$1`, `$2`, etc., and named fields by their name (e.g., `.a`). 171 | 9. Records are immutable: fields do not have setters. 172 | 10. Records are structurally typed: the set, types, and names of fields define the record's type (shape). 173 | 11. Two records are equal if they have the same shape and all corresponding field values are equal. Named field order does not affect equality. 174 | 12. Records automatically define `hashCode` and `==` based on structure and field values. 175 | 13. Use records for functions that return multiple values; destructure with pattern matching: `var (name, age) = userInfo(json);` 176 | 14. Destructure named fields with the colon syntax: `final (:name, :age) = userInfo(json);` 177 | 15. Using records for multiple returns is more concise and type-safe than using classes, lists, or maps. 178 | 16. Use lists of records for simple data tuples with the same shape. 179 | 17. Use type aliases (`typedef`) for record types to improve readability and maintainability. 180 | 18. Changing a record type alias does not guarantee all code using it is still type-safe; only classes provide full abstraction/encapsulation. 181 | 19. Extension types can wrap records but do not provide full abstraction or protection. 182 | 20. Records are best for simple, immutable data aggregation; use classes for abstraction, encapsulation, and behavior. 183 | 184 | # Common Flutter Errors 185 | 186 | 1. If you get a "RenderFlex overflowed" error, check if a `Row` or `Column` contains unconstrained widgets. Fix by wrapping children in `Flexible`, `Expanded`, or by setting constraints. 187 | 2. If you get "Vertical viewport was given unbounded height", ensure `ListView` or similar scrollable widgets inside a `Column` have a bounded height (e.g., wrap with `Expanded` or `SizedBox`). 188 | 3. If you get "An InputDecorator...cannot have an unbounded width", constrain the width of widgets like `TextField` using `Expanded`, `SizedBox`, or by placing them in a parent with width constraints. 189 | 4. If you get a "setState called during build" error, do not call `setState` or `showDialog` directly inside the build method. Trigger dialogs or state changes in response to user actions or after the build completes (e.g., using `addPostFrameCallback`). 190 | 5. If you get "The ScrollController is attached to multiple scroll views", make sure each `ScrollController` is only attached to a single scrollable widget at a time. 191 | 6. If you get a "RenderBox was not laid out" error, check for missing or unbounded constraints in your widget tree. This is often caused by using widgets like `ListView` or `Column` without proper size constraints. 192 | 7. Use the Flutter Inspector and review widget constraints to debug layout issues. Refer to the official documentation on constraints if needed. -------------------------------------------------------------------------------- /combined/flutter_dart__under_6K.md: -------------------------------------------------------------------------------- 1 | # Effective Dart Rules 2 | 3 | ### Naming Conventions 4 | 1. Use terms consistently throughout your code. 5 | 2. Name types using `UpperCamelCase` (classes, enums, typedefs, type parameters). 6 | 3. Name extensions using `UpperCamelCase`. 7 | 4. Name packages, directories, and source files using `lowercase_with_underscores`. 8 | 5. Name import prefixes using `lowercase_with_underscores`. 9 | 6. Name other identifiers using `lowerCamelCase` (variables, parameters, named parameters). 10 | 7. Capitalize acronyms and abbreviations longer than two letters like words. 11 | 8. Avoid abbreviations unless the abbreviation is more common than the unabbreviated term. 12 | 9. Prefer putting the most descriptive noun last in names. 13 | 10. Prefer a noun phrase for non-boolean properties or variables. 14 | 15 | ### Types and Functions 16 | 1. Use class modifiers to control if your class can be extended or used as an interface. 17 | 2. Type annotate fields and top-level variables if the type isn't obvious. 18 | 3. Annotate return types on function declarations. 19 | 4. Annotate parameter types on function declarations. 20 | 5. Use `Future` as the return type of asynchronous members that do not produce values. 21 | 6. Use getters for operations that conceptually access properties. 22 | 7. Use setters for operations that conceptually change properties. 23 | 8. Use inclusive start and exclusive end parameters to accept a range. 24 | 25 | ### Style and Structure 26 | 1. Prefer `final` over `var` when variable values won't change. 27 | 2. Use `const` for compile-time constants. 28 | 3. Keep files focused on a single responsibility. 29 | 4. Limit file length to maintain readability. 30 | 5. Group related functionality together. 31 | 6. Prefer making declarations private. 32 | 33 | ### Imports & Files 34 | 1. Don't import libraries inside the `src` directory of another package. 35 | 2. Prefer relative import paths within a package. 36 | 3. Don't use `/lib/` or `../` in import paths. 37 | 4. Consider writing a library-level doc comment for library files. 38 | 39 | ### Usage 40 | 1. Use strings in `part of` directives. 41 | 2. Use adjacent strings to concatenate string literals. 42 | 3. Use collection literals when possible. 43 | 4. Use `whereType()` to filter a collection by type. 44 | 5. Test for `Future` when disambiguating a `FutureOr` whose type argument could be `Object`. 45 | 6. Initialize fields at their declaration when possible. 46 | 7. Use initializing formals when possible. 47 | 8. Use `;` instead of `{}` for empty constructor bodies. 48 | 9. Use `rethrow` to rethrow a caught exception. 49 | 10. Override `hashCode` if you override `==`. 50 | 11. Make your `==` operator obey the mathematical rules of equality. 51 | 52 | ### Documentation 53 | 1. Use `///` doc comments to document members and types; don't use block comments for documentation. 54 | 2. Prefer writing doc comments for public APIs. 55 | 3. Start doc comments with a single-sentence summary. 56 | 4. Use square brackets in doc comments to refer to in-scope identifiers. 57 | 58 | ### Flutter Best Practices 59 | 1. Extract reusable widgets into separate components. 60 | 2. Use `StatelessWidget` when possible. 61 | 3. Keep build methods simple and focused. 62 | 4. Avoid unnecessary `StatefulWidget`s. 63 | 5. Keep state as local as possible. 64 | 6. Use `const` constructors when possible. 65 | 7. Avoid expensive operations in build methods. 66 | 8. Implement pagination for large lists. 67 | 68 | ### Dart 3: Records 69 | 1. Records are anonymous, immutable, aggregate types that bundle multiple objects into a single value. 70 | 2. Records are fixed-sized, heterogeneous, and strongly typed. Each field can have a different type. 71 | 3. Record expressions use parentheses with comma-delimited positional and/or named fields, e.g. `('first', a: 2, b: true, 'last')`. 72 | 4. Record fields are accessed via built-in getters: positional fields as `$1`, `$2`, etc., and named fields by their name (e.g., `.a`). 73 | 5. Records are immutable: fields do not have setters. 74 | 6. Use records for functions that return multiple values; destructure with pattern matching: `var (name, age) = userInfo(json);` 75 | 7. Use type aliases (`typedef`) for record types to improve readability and maintainability. 76 | 8. Records are best for simple, immutable data aggregation; use classes for abstraction, encapsulation, and behavior. 77 | 78 | ### Dart 3: Patterns 79 | 1. Patterns represent the shape of values for matching and destructuring. 80 | 2. Pattern matching checks if a value has a certain shape, constant, equality, or type. 81 | 3. Pattern destructuring allows extracting parts of a matched value and binding them to variables. 82 | 4. Use wildcard patterns (`_`) to ignore parts of a matched value. 83 | 5. Use rest elements (`...`, `...rest`) in list patterns to match arbitrary-length lists. 84 | 6. Use logical-or patterns (e.g., `case a || b`) to match multiple alternatives in a single case. 85 | 7. Add guard clauses (`when`) to further constrain when a case matches. 86 | 8. Use the `sealed` modifier on a class to enable exhaustiveness checking when switching over its subtypes. 87 | 88 | ### Common Flutter Errors 89 | 1. If you get a "RenderFlex overflowed" error, check if a `Row` or `Column` contains unconstrained widgets. Fix by wrapping children in `Flexible`, `Expanded`, or by setting constraints. 90 | 2. If you get "Vertical viewport was given unbounded height", ensure `ListView` or similar scrollable widgets inside a `Column` have a bounded height. 91 | 3. If you get "An InputDecorator...cannot have an unbounded width", constrain the width of widgets like `TextField`. 92 | 4. If you get a "setState called during build" error, do not call `setState` or `showDialog` directly inside the build method. 93 | 5. If you get "The ScrollController is attached to multiple scroll views", make sure each `ScrollController` is only attached to a single scrollable widget. 94 | 6. If you get a "RenderBox was not laid out" error, check for missing or unbounded constraints in your widget tree. 95 | 7. Use the Flutter Inspector and review widget constraints to debug layout issues. 96 | 97 | ### Testing 98 | 1. Write unit tests for business logic. 99 | 2. Write widget tests for UI components. -------------------------------------------------------------------------------- /combined/flutter_dart_bloc_mocktail__under_6K.md: -------------------------------------------------------------------------------- 1 | # Effective Dart Rules 2 | 3 | ### Key Rules 4 | 1. Use class modifiers to control if your class can be extended or used as an interface. 5 | 2. Make your `==` operator obey the mathematical rules of equality and override `hashCode` if you override `==`. 6 | 3. Type annotate fields, variables, and parameters when the type isn't obvious. 7 | 4. Use `Future` as the return type of asynchronous members that do not produce values. 8 | 5. Use getters for operations that conceptually access properties and setters for operations that conceptually change properties. 9 | 6. Use collection literals when possible. 10 | 7. Use `whereType()` to filter a collection by type. 11 | 8. Initialize fields at their declaration when possible. 12 | 9. Use initializing formals when possible. 13 | 10. Use `rethrow` to rethrow a caught exception. 14 | 15 | ### Style & Structure 16 | 1. Prefer `final` over `var` when variable values won't change. 17 | 2. Use `const` for compile-time constants. 18 | 3. Keep files focused on a single responsibility. 19 | 4. Prefer making declarations private. 20 | 5. Prefer making fields and top-level variables `final`. 21 | 6. Consider making your constructor `const` if the class supports it. 22 | 23 | # Dart 3 Updates 24 | 25 | ### Key Features 26 | 1. Use records to group multiple values into a single, immutable object. 27 | 2. Access record fields by position (`$1`, `$2`, ...) or by name. 28 | 3. Use record types as return types or parameters for functions that return multiple values. 29 | 4. Use patterns to destructure records, lists, and objects directly in variable declarations, switch statements, and if-case. 30 | 5. Use switch expressions and pattern matching for concise and exhaustive control flow. 31 | 6. Use sealed classes to restrict which classes can implement or extend a base class. 32 | 7. Use class modifiers: `base`, `interface`, `final`, and `sealed`. 33 | 34 | # Flutter Best Practices 35 | 36 | ### Widgets & UI 37 | 1. Extract reusable widgets into separate components. 38 | 2. Use `StatelessWidget` when possible and avoid unnecessary `StatefulWidgets`. 39 | 3. Keep build methods simple and focused. 40 | 4. Keep state as local as possible. 41 | 5. Use `const` constructors when possible. 42 | 6. Avoid expensive operations in build methods. 43 | 7. Implement pagination for large lists. 44 | 45 | # Common Flutter Errors 46 | 47 | 1. If you get a "RenderFlex overflowed" error, check if a `Row` or `Column` contains unconstrained widgets. 48 | 2. If you get "Vertical viewport was given unbounded height", ensure `ListView` or similar scrollable widgets inside a `Column` have a bounded height. 49 | 3. If you get "An InputDecorator...cannot have an unbounded width", constrain the width of widgets like `TextField`. 50 | 4. If you get a "setState called during build" error, do not call `setState` or `showDialog` directly inside the build method. 51 | 5. If you get "The ScrollController is attached to multiple scroll views", make sure each `ScrollController` is only attached to a single scrollable widget at a time. 52 | 53 | # Bloc Rules 54 | 55 | ### Naming Conventions 56 | 1. Events should be named in the past tense, reflecting actions that have already occurred. 57 | 2. States should be nouns. 58 | 59 | ### State Modeling 60 | 1. Prefer sealed classes or subclasses for exclusive states to enforce exhaustiveness and type safety. 61 | 2. State classes should extend `Equatable`, use `@immutable`, implement a `copyWith` method, and use `const` constructors where possible. 62 | 63 | ### Bloc Architecture 64 | 1. Implement a custom `BlocObserver` to monitor all state changes and errors across blocs. 65 | 2. Keep business logic inside blocs/cubits, not in UI widgets. 66 | 3. Only emit new states when the data actually changes to avoid unnecessary rebuilds. 67 | 4. Separate your features into three layers: Presentation, Business Logic, and Data. 68 | 69 | ### Flutter Bloc Integration 70 | 1. Use `BlocProvider` to provide blocs to widget subtrees. 71 | 2. Use `BlocBuilder` to rebuild widgets in response to state changes. 72 | 3. Use `BlocListener` for side effects in response to state changes. 73 | 4. Use `context.select` for fine-grained rebuilds. 74 | 75 | ### Bloc Do's and Don'ts 76 | 1. Do not mutate state directly; always emit a new state. 77 | 2. Do not perform side effects inside blocs; use the UI layer or listen to state changes for side effects. 78 | 3. Do not use `context.read` in the build method to access state; use `BlocBuilder` or `context.watch` instead. 79 | 80 | # Mocktail Rules 81 | 82 | 1. Use a `Fake` when you need a lightweight, custom implementation of a class for testing. 83 | 2. Use a `Mock` when you need to verify interactions or stub method responses. 84 | 3. Use `registerFallbackValue` to register a default value for a type used as an argument in a mock method. 85 | 4. Use `when(() => mock.method()).thenReturn(value)` to stub method calls, and `thenThrow(error)` to stub errors. 86 | 5. Use `verify(() => mock.method())` to check if a method was called; use `verifyNever(() => mock.method())` to check it was never called. 87 | 6. Always stub async methods (returning `Future` or `Future`) with `thenAnswer((_) async {})` or `thenReturn(Future.value(...))`. 88 | -------------------------------------------------------------------------------- /combined/flutter_dart_change_notifier.md: -------------------------------------------------------------------------------- 1 | # Effective Dart Rules 2 | 3 | ### Naming Conventions 4 | 1. Use terms consistently throughout your code. 5 | 2. Follow existing mnemonic conventions when naming type parameters (e.g., `E` for element, `K`/`V` for key/value, `T`/`S`/`U` for generic types). 6 | 3. Name types using `UpperCamelCase` (classes, enums, typedefs, type parameters). 7 | 4. Name extensions using `UpperCamelCase`. 8 | 5. Name packages, directories, and source files using `lowercase_with_underscores`. 9 | 6. Name import prefixes using `lowercase_with_underscores`. 10 | 7. Name other identifiers using `lowerCamelCase` (variables, parameters, named parameters). 11 | 8. Capitalize acronyms and abbreviations longer than two letters like words. 12 | 9. Avoid abbreviations unless the abbreviation is more common than the unabbreviated term. 13 | 10. Prefer putting the most descriptive noun last in names. 14 | 11. Consider making code read like a sentence when designing APIs. 15 | 12. Prefer a noun phrase for non-boolean properties or variables. 16 | 13. Prefer a non-imperative verb phrase for boolean properties or variables. 17 | 14. Prefer the positive form for boolean property and variable names. 18 | 15. Consider omitting the verb for named boolean parameters. 19 | 16. Use camelCase for variable and function names. 20 | 17. Use PascalCase for class names. 21 | 18. Use snake_case for file names. 22 | 23 | ### Types and Functions 24 | 1. Use class modifiers to control if your class can be extended or used as an interface. 25 | 2. Type annotate variables without initializers. 26 | 3. Type annotate fields and top-level variables if the type isn't obvious. 27 | 4. Annotate return types on function declarations. 28 | 5. Annotate parameter types on function declarations. 29 | 6. Write type arguments on generic invocations that aren't inferred. 30 | 7. Annotate with `dynamic` instead of letting inference fail. 31 | 8. Use `Future` as the return type of asynchronous members that do not produce values. 32 | 9. Use getters for operations that conceptually access properties. 33 | 10. Use setters for operations that conceptually change properties. 34 | 11. Use a function declaration to bind a function to a name. 35 | 12. Use inclusive start and exclusive end parameters to accept a range. 36 | 37 | ### Style 38 | 1. Format your code using `dart format`. 39 | 2. Use curly braces for all flow control statements. 40 | 3. Prefer `final` over `var` when variable values won't change. 41 | 4. Use `const` for compile-time constants. 42 | 43 | ### Imports & Files 44 | 1. Don't import libraries inside the `src` directory of another package. 45 | 2. Don't allow import paths to reach into or out of `lib`. 46 | 3. Prefer relative import paths within a package. 47 | 4. Don't use `/lib/` or `../` in import paths. 48 | 5. Consider writing a library-level doc comment for library files. 49 | 50 | ### Structure 51 | 1. Keep files focused on a single responsibility. 52 | 2. Limit file length to maintain readability. 53 | 3. Group related functionality together. 54 | 4. Prefer making fields and top-level variables `final`. 55 | 5. Consider making your constructor `const` if the class supports it. 56 | 6. Prefer making declarations private. 57 | 58 | ### Usage 59 | 1. Use strings in `part of` directives. 60 | 2. Use adjacent strings to concatenate string literals. 61 | 3. Use collection literals when possible. 62 | 4. Use `whereType()` to filter a collection by type. 63 | 5. Test for `Future` when disambiguating a `FutureOr` whose type argument could be `Object`. 64 | 6. Follow a consistent rule for `var` and `final` on local variables. 65 | 7. Initialize fields at their declaration when possible. 66 | 8. Use initializing formals when possible. 67 | 9. Use `;` instead of `{}` for empty constructor bodies. 68 | 10. Use `rethrow` to rethrow a caught exception. 69 | 11. Override `hashCode` if you override `==`. 70 | 12. Make your `==` operator obey the mathematical rules of equality. 71 | 72 | ### Documentation 73 | 1. Format comments like sentences. 74 | 2. Use `///` doc comments to document members and types; don't use block comments for documentation. 75 | 3. Prefer writing doc comments for public APIs. 76 | 4. Consider writing doc comments for private APIs. 77 | 5. Consider including explanations of terminology, links, and references in library-level docs. 78 | 6. Start doc comments with a single-sentence summary. 79 | 7. Separate the first sentence of a doc comment into its own paragraph. 80 | 8. Use square brackets in doc comments to refer to in-scope identifiers. 81 | 9. Use prose to explain parameters, return values, and exceptions. 82 | 10. Put doc comments before metadata annotations. 83 | 11. Document why code exists or how it should be used, not just what it does. 84 | 85 | ### Testing 86 | 1. Write unit tests for business logic. 87 | 2. Write widget tests for UI components. 88 | 3. Aim for good test coverage. 89 | 90 | ### Widgets 91 | 1. Extract reusable widgets into separate components. 92 | 2. Use `StatelessWidget` when possible. 93 | 3. Keep build methods simple and focused. 94 | 95 | ### State Management 96 | 1. Choose appropriate state management based on complexity. 97 | 2. Avoid unnecessary `StatefulWidget`s. 98 | 3. Keep state as local as possible. 99 | 100 | ### Performance 101 | 1. Use `const` constructors when possible. 102 | 2. Avoid expensive operations in build methods. 103 | 3. Implement pagination for large lists. 104 | 105 | # Dart 3 Updates 106 | 107 | ### Branches 108 | 1. Use `if` statements for conditional branching. The condition must evaluate to a boolean. 109 | 2. `if` statements support optional `else` and `else if` clauses for multiple branches. 110 | 3. Use `if-case` statements to match and destructure a value against a single pattern. Example: `if (pair case [int x, int y]) { ... }` 111 | 4. If the pattern in an `if-case` matches, variables defined in the pattern are in scope for that branch. 112 | 5. If the pattern does not match in an `if-case`, control flows to the `else` branch if present. 113 | 6. Use `switch` statements to match a value against multiple patterns (cases). Each `case` can use any kind of pattern. 114 | 7. When a value matches a `case` pattern in a `switch` statement, the case body executes and control jumps to the end of the switch. `break` is not required. 115 | 8. You can end a non-empty `case` clause with `continue`, `throw`, or `return`. 116 | 9. Use `default` or `_` in a `switch` statement to handle unmatched values. 117 | 10. Empty `case` clauses fall through to the next case. Use `break` to prevent fallthrough. 118 | 11. Use `continue` with a label for non-sequential fallthrough between cases. 119 | 12. Use logical-or patterns (e.g., `case a || b`) to share a body or guard between cases. 120 | 13. Use `switch` expressions to produce a value based on matching cases. Syntax differs from statements: omit `case`, use `=>` for bodies, and separate cases with commas. 121 | 14. In `switch` expressions, the default case must use `_` (not `default`). 122 | 15. Dart checks for exhaustiveness in `switch` statements and expressions, reporting a compile-time error if not all possible values are handled. 123 | 16. To ensure exhaustiveness, use a default (`default` or `_`) case, or switch over enums or sealed types. 124 | 17. Use the `sealed` modifier on a class to enable exhaustiveness checking when switching over its subtypes. 125 | 18. Add a guard clause to a `case` using `when` to further constrain when a case matches. Example: `case pattern when condition:` 126 | 19. Guard clauses can be used in `if-case`, `switch` statements, and `switch` expressions. The guard is evaluated after pattern matching. 127 | 20. If a guard clause evaluates to false, execution proceeds to the next case (does not exit the switch). 128 | 129 | ### Patterns 130 | 1. Patterns are a syntactic category that represent the shape of values for matching and destructuring. 131 | 2. Pattern matching checks if a value has a certain shape, constant, equality, or type. 132 | 3. Pattern destructuring allows extracting parts of a matched value and binding them to variables. 133 | 4. Patterns can be nested, using subpatterns (outer/inner patterns) for recursive matching and destructuring. 134 | 5. Use wildcard patterns (`_`) to ignore parts of a matched value; use rest elements in list patterns to ignore remaining elements. 135 | 6. Patterns can be used in: 136 | - Local variable declarations and assignments 137 | - For and for-in loops 138 | - If-case and switch-case statements 139 | - Control flow in collection literals 140 | 7. Pattern variable declarations start with `var` or `final` and bind new variables from the matched value. Example: `var (a, [b, c]) = ('str', [1, 2]);` 141 | 8. Pattern variable assignments destructure a value and assign to existing variables. Example: `(b, a) = (a, b); // swap values` 142 | 9. Every case clause in `switch` and `if-case` contains a pattern. Any kind of pattern can be used in a case. 143 | 10. Case patterns are refutable; if the pattern doesn't match, execution continues to the next case. 144 | 11. Destructured values in a case become local variables scoped to the case body. 145 | 12. Use logical-or patterns (e.g., `case a || b`) to match multiple alternatives in a single case. 146 | 13. Use logical-or patterns with guards (`when`) to share a body or guard between cases. 147 | 14. Guard clauses (`when`) evaluate a condition after matching; if false, execution proceeds to the next case. 148 | 15. Patterns can be used in for and for-in loops to destructure collection elements (e.g., destructuring `MapEntry` in map iteration). 149 | 16. Object patterns match named object types and destructure their data using getters. Example: `var Foo(:one, :two) = myFoo;` 150 | 8. Constant patterns match if the value is equal to a constant (number, string, bool, named constant, const constructor, const collection, etc.). Use parentheses and `const` for complex expressions. 151 | 9. Variable patterns (`var name`, `final Type name`) bind new variables to matched/destructured values. Typed variable patterns only match if the value has the declared type. 152 | 10. Identifier patterns (`foo`, `_`) act as variable or constant patterns depending on context. `_` always acts as a wildcard and matches/discards any value. 153 | 11. Parenthesized patterns (`(subpattern)`) control pattern precedence and grouping, similar to expressions. 154 | 12. List patterns (`[subpattern1, subpattern2]`) match lists and destructure elements by position. The pattern length must match the list unless a rest element is used. 155 | 13. Rest elements (`...`, `...rest`) in list patterns match arbitrary-length lists or collect unmatched elements into a new list. 156 | 14. Map patterns (`{"key": subpattern}`) match maps and destructure by key. Only specified keys are matched; missing keys throw a `StateError`. 157 | 15. Record patterns (`(subpattern1, subpattern2)`, `(x: subpattern1, y: subpattern2)`) match records by shape and destructure positional/named fields. Field names can be omitted if inferred from variable or identifier patterns. 158 | 16. Object patterns (`ClassName(field1: subpattern1, field2: subpattern2)`) match objects by type and destructure using getters. Extra fields in the object are ignored. 159 | 17. Wildcard patterns (`_`, `Type _`) match any value without binding. Useful for ignoring values or type-checking without binding. 160 | 18. All pattern types can be nested and combined for expressive and precise matching and destructuring. 161 | 162 | ### Records 163 | 1. Records are anonymous, immutable, aggregate types that bundle multiple objects into a single value. 164 | 2. Records are fixed-sized, heterogeneous, and strongly typed. Each field can have a different type. 165 | 3. Records are real values: store them in variables, nest them, pass to/from functions, and use in lists, maps, and sets. 166 | 4. Record expressions use parentheses with comma-delimited positional and/or named fields, e.g. `('first', a: 2, b: true, 'last')`. 167 | 5. Record type annotations use parentheses with comma-delimited types. Named fields use curly braces: `({int a, bool b})`. 168 | 6. The names of named fields are part of the record's type (shape). Records with different named field names have different types. 169 | 7. Positional field names in type annotations are for documentation only and do not affect the record's type. 170 | 8. Record fields are accessed via built-in getters: positional fields as `$1`, `$2`, etc., and named fields by their name (e.g., `.a`). 171 | 9. Records are immutable: fields do not have setters. 172 | 10. Records are structurally typed: the set, types, and names of fields define the record's type (shape). 173 | 11. Two records are equal if they have the same shape and all corresponding field values are equal. Named field order does not affect equality. 174 | 12. Records automatically define `hashCode` and `==` based on structure and field values. 175 | 13. Use records for functions that return multiple values; destructure with pattern matching: `var (name, age) = userInfo(json);` 176 | 14. Destructure named fields with the colon syntax: `final (:name, :age) = userInfo(json);` 177 | 15. Using records for multiple returns is more concise and type-safe than using classes, lists, or maps. 178 | 16. Use lists of records for simple data tuples with the same shape. 179 | 17. Use type aliases (`typedef`) for record types to improve readability and maintainability. 180 | 18. Changing a record type alias does not guarantee all code using it is still type-safe; only classes provide full abstraction/encapsulation. 181 | 19. Extension types can wrap records but do not provide full abstraction or protection. 182 | 20. Records are best for simple, immutable data aggregation; use classes for abstraction, encapsulation, and behavior. 183 | 184 | # Common Flutter Errors 185 | 186 | 1. If you get a "RenderFlex overflowed" error, check if a `Row` or `Column` contains unconstrained widgets. Fix by wrapping children in `Flexible`, `Expanded`, or by setting constraints. 187 | 2. If you get "Vertical viewport was given unbounded height", ensure `ListView` or similar scrollable widgets inside a `Column` have a bounded height (e.g., wrap with `Expanded` or `SizedBox`). 188 | 3. If you get "An InputDecorator...cannot have an unbounded width", constrain the width of widgets like `TextField` using `Expanded`, `SizedBox`, or by placing them in a parent with width constraints. 189 | 4. If you get a "setState called during build" error, do not call `setState` or `showDialog` directly inside the build method. Trigger dialogs or state changes in response to user actions or after the build completes (e.g., using `addPostFrameCallback`). 190 | 5. If you get "The ScrollController is attached to multiple scroll views", make sure each `ScrollController` is only attached to a single scrollable widget at a time. 191 | 6. If you get a "RenderBox was not laid out" error, check for missing or unbounded constraints in your widget tree. This is often caused by using widgets like `ListView` or `Column` without proper size constraints. 192 | 7. Use the Flutter Inspector and review widget constraints to debug layout issues. Refer to the official documentation on constraints if needed. 193 | ### Flutter ChangeNotifier State Management Rules 194 | 195 | 1. Place shared state above the widgets that use it in the widget tree to enable proper rebuilds and avoid imperative UI updates. 196 | 2. Avoid directly mutating widgets or calling methods on them to change state; instead, rebuild widgets with new data. 197 | 3. Use a model class that extends `ChangeNotifier` to manage and notify listeners of state changes. 198 | ```dart 199 | class CartModel extends ChangeNotifier { 200 | final List _items = []; 201 | UnmodifiableListView get items => UnmodifiableListView(_items); 202 | 203 | void add(Item item) { 204 | _items.add(item); 205 | notifyListeners(); 206 | } 207 | } 208 | ``` 209 | 4. Keep internal state private within the model and expose unmodifiable views to the UI. 210 | 5. Call `notifyListeners()` in your model whenever the state changes to trigger UI rebuilds. 211 | 6. Use `ChangeNotifierProvider` to provide your model to the widget subtree that needs access to it. 212 | ```dart 213 | ChangeNotifierProvider( 214 | create: (context) => CartModel(), 215 | child: MyApp(), 216 | ) 217 | ``` 218 | 7. Wrap widgets that depend on the model’s state in a `Consumer` widget to rebuild only when relevant data changes. 219 | ```dart 220 | return Consumer( 221 | builder: (context, cart, child) => Stack( 222 | children: [ 223 | if (child != null) child, 224 | Text('Total price: \${cart.totalPrice}'), 225 | ], 226 | ), 227 | child: const SomeExpensiveWidget(), 228 | ); 229 | ``` 230 | 8. Always specify the generic type `` for `Consumer` and `Provider.of` to ensure type safety and correct behavior. 231 | 9. Use the `child` parameter of `Consumer` to optimize performance by preventing unnecessary rebuilds of widgets that do not depend on the model. 232 | 10. Place `Consumer` widgets as deep in the widget tree as possible to minimize the scope of rebuilds. 233 | ```dart 234 | return HumongousWidget( 235 | child: AnotherMonstrousWidget( 236 | child: Consumer( 237 | builder: (context, cart, child) { 238 | return Text('Total price: \${cart.totalPrice}'); 239 | }, 240 | ), 241 | ), 242 | ); 243 | ``` 244 | 11. Do not wrap large widget subtrees in a `Consumer` if only a small part depends on the model; instead, wrap only the part that needs to rebuild. 245 | 12. Use `Provider.of(context, listen: false)` when you need to access the model for actions (such as calling methods) but do not want the widget to rebuild on state changes. 246 | ```dart 247 | Provider.of(context, listen: false).removeAll(); 248 | ``` 249 | 13. `ChangeNotifierProvider` automatically disposes of the model when it is no longer needed. 250 | 14. Use `MultiProvider` when you need to provide multiple models to the widget tree. 251 | 15. Write unit tests for your `ChangeNotifier` models to verify state changes and notifications. 252 | 16. Avoid rebuilding widgets unnecessarily; optimize rebuilds by structuring your widget tree and provider usage carefully. 253 | 254 | 255 | -------------------------------------------------------------------------------- /combined/flutter_dart_change_notifier__under_6K.md: -------------------------------------------------------------------------------- 1 | # Flutter with ChangeNotifier Rules 2 | 3 | ## Core Dart Principles 4 | 1. Use class modifiers to control if your class can be extended or used as an interface. 5 | 2. Make your `==` operator obey the mathematical rules of equality and override `hashCode` if you override `==`. 6 | 3. Type annotate fields, variables, and parameters when the type isn't obvious. 7 | 4. Use `Future` as the return type of asynchronous members that do not produce values. 8 | 5. Use getters for operations that conceptually access properties and setters for operations that conceptually change properties. 9 | 6. Use collection literals when possible. 10 | 7. Use `whereType()` to filter a collection by type. 11 | 8. Initialize fields at their declaration when possible. 12 | 9. Use initializing formals when possible. 13 | 10. Use `rethrow` to rethrow a caught exception. 14 | 15 | ## Flutter Best Practices 16 | 1. Extract reusable widgets into separate components. 17 | 2. Use `StatelessWidget` when possible. 18 | 3. Keep build methods simple and focused. 19 | 4. Avoid unnecessary `StatefulWidget`s. 20 | 5. Keep state as local as possible. 21 | 6. Use `const` constructors when possible. 22 | 7. Avoid expensive operations in build methods. 23 | 8. Implement pagination for large lists. 24 | 25 | ## ChangeNotifier State Management 26 | 1. Place shared state above the widgets that use it in the widget tree to enable proper rebuilds and avoid imperative UI updates. 27 | 2. Avoid directly mutating widgets or calling methods on them to change state; instead, rebuild widgets with new data. 28 | 3. Use a model class that extends `ChangeNotifier` to manage and notify listeners of state changes. 29 | ```dart 30 | class CartModel extends ChangeNotifier { 31 | final List _items = []; 32 | UnmodifiableListView get items => UnmodifiableListView(_items); 33 | 34 | void add(Item item) { 35 | _items.add(item); 36 | notifyListeners(); 37 | } 38 | } 39 | ``` 40 | 4. Keep internal state private within the model and expose unmodifiable views to the UI. 41 | 5. Call `notifyListeners()` in your model whenever the state changes to trigger UI rebuilds. 42 | 6. Use `ChangeNotifierProvider` to provide your model to the widget subtree that needs access to it. 43 | ```dart 44 | ChangeNotifierProvider( 45 | create: (context) => CartModel(), 46 | child: MyApp(), 47 | ) 48 | ``` 49 | 7. Wrap widgets that depend on the model's state in a `Consumer` widget to rebuild only when relevant data changes. 50 | ```dart 51 | return Consumer( 52 | builder: (context, cart, child) => Stack( 53 | children: [ 54 | if (child != null) child, 55 | Text('Total price: \${cart.totalPrice}'), 56 | ], 57 | ), 58 | child: const SomeExpensiveWidget(), 59 | ); 60 | ``` 61 | 8. Always specify the generic type `` for `Consumer` and `Provider.of` to ensure type safety and correct behavior. 62 | 9. Use the `child` parameter of `Consumer` to optimize performance by preventing unnecessary rebuilds of widgets that do not depend on the model. 63 | 10. Place `Consumer` widgets as deep in the widget tree as possible to minimize the scope of rebuilds. 64 | ```dart 65 | return HumongousWidget( 66 | child: AnotherMonstrousWidget( 67 | child: Consumer( 68 | builder: (context, cart, child) { 69 | return Text('Total price: \${cart.totalPrice}'); 70 | }, 71 | ), 72 | ), 73 | ); 74 | ``` 75 | 11. Do not wrap large widget subtrees in a `Consumer` if only a small part depends on the model; instead, wrap only the part that needs to rebuild. 76 | 12. Use `Provider.of(context, listen: false)` when you need to access the model for actions (such as calling methods) but do not want the widget to rebuild on state changes. 77 | ```dart 78 | Provider.of(context, listen: false).removeAll(); 79 | ``` 80 | 13. `ChangeNotifierProvider` automatically disposes of the model when it is no longer needed. 81 | 14. Use `MultiProvider` when you need to provide multiple models to the widget tree. 82 | 15. Write unit tests for your `ChangeNotifier` models to verify state changes and notifications. 83 | 16. Avoid rebuilding widgets unnecessarily; optimize rebuilds by structuring your widget tree and provider usage carefully. 84 | 85 | ## Dart 3 Modern Features 86 | 1. Use records for grouping values: `var user = ('John', age: 30)`. 87 | 2. Access record fields by position (`$1`) or name (`.age`). 88 | 3. Use patterns to destructure records, lists, and objects: `var (name, age) = user;` 89 | 4. Use switch expressions and pattern matching for concise, exhaustive control flow. 90 | 5. Use sealed classes for exhaustive `switch` and type safety. 91 | 92 | ## Common Flutter Errors 93 | 1. If you get a "RenderFlex overflowed" error, check if a `Row` or `Column` contains unconstrained widgets. Fix by wrapping children in `Flexible`, `Expanded`, or by setting constraints. 94 | 2. If you get "Vertical viewport was given unbounded height", ensure `ListView` or similar scrollable widgets inside a `Column` have a bounded height (e.g., wrap with `Expanded` or `SizedBox`). 95 | 3. If you get "An InputDecorator...cannot have an unbounded width", constrain the width of widgets like `TextField` using `Expanded`, `SizedBox`, or by placing them in a parent with width constraints. 96 | 4. If you get a "setState called during build" error, do not call `setState` or `showDialog` directly inside the build method. Trigger dialogs or state changes in response to user actions or after the build completes (e.g., using `addPostFrameCallback`). 97 | 5. If you get "The ScrollController is attached to multiple scroll views", make sure each `ScrollController` is only attached to a single scrollable widget at a time. 98 | 6. If you get a "RenderBox was not laid out" error, check for missing or unbounded constraints in your widget tree. This is often caused by using widgets like `ListView` or `Column` without proper size constraints. 99 | -------------------------------------------------------------------------------- /combined/flutter_dart_provider.md: -------------------------------------------------------------------------------- 1 | # Effective Dart Rules 2 | 3 | ### Naming Conventions 4 | 1. Use terms consistently throughout your code. 5 | 2. Follow existing mnemonic conventions when naming type parameters (e.g., `E` for element, `K`/`V` for key/value, `T`/`S`/`U` for generic types). 6 | 3. Name types using `UpperCamelCase` (classes, enums, typedefs, type parameters). 7 | 4. Name extensions using `UpperCamelCase`. 8 | 5. Name packages, directories, and source files using `lowercase_with_underscores`. 9 | 6. Name import prefixes using `lowercase_with_underscores`. 10 | 7. Name other identifiers using `lowerCamelCase` (variables, parameters, named parameters). 11 | 8. Capitalize acronyms and abbreviations longer than two letters like words. 12 | 9. Avoid abbreviations unless the abbreviation is more common than the unabbreviated term. 13 | 10. Prefer putting the most descriptive noun last in names. 14 | 11. Consider making code read like a sentence when designing APIs. 15 | 12. Prefer a noun phrase for non-boolean properties or variables. 16 | 13. Prefer a non-imperative verb phrase for boolean properties or variables. 17 | 14. Prefer the positive form for boolean property and variable names. 18 | 15. Consider omitting the verb for named boolean parameters. 19 | 16. Use camelCase for variable and function names. 20 | 17. Use PascalCase for class names. 21 | 18. Use snake_case for file names. 22 | 23 | ### Types and Functions 24 | 1. Use class modifiers to control if your class can be extended or used as an interface. 25 | 2. Type annotate variables without initializers. 26 | 3. Type annotate fields and top-level variables if the type isn't obvious. 27 | 4. Annotate return types on function declarations. 28 | 5. Annotate parameter types on function declarations. 29 | 6. Write type arguments on generic invocations that aren't inferred. 30 | 7. Annotate with `dynamic` instead of letting inference fail. 31 | 8. Use `Future` as the return type of asynchronous members that do not produce values. 32 | 9. Use getters for operations that conceptually access properties. 33 | 10. Use setters for operations that conceptually change properties. 34 | 11. Use a function declaration to bind a function to a name. 35 | 12. Use inclusive start and exclusive end parameters to accept a range. 36 | 37 | ### Style 38 | 1. Format your code using `dart format`. 39 | 2. Use curly braces for all flow control statements. 40 | 3. Prefer `final` over `var` when variable values won't change. 41 | 4. Use `const` for compile-time constants. 42 | 43 | ### Imports & Files 44 | 1. Don't import libraries inside the `src` directory of another package. 45 | 2. Don't allow import paths to reach into or out of `lib`. 46 | 3. Prefer relative import paths within a package. 47 | 4. Don't use `/lib/` or `../` in import paths. 48 | 5. Consider writing a library-level doc comment for library files. 49 | 50 | ### Structure 51 | 1. Keep files focused on a single responsibility. 52 | 2. Limit file length to maintain readability. 53 | 3. Group related functionality together. 54 | 4. Prefer making fields and top-level variables `final`. 55 | 5. Consider making your constructor `const` if the class supports it. 56 | 6. Prefer making declarations private. 57 | 58 | ### Usage 59 | 1. Use strings in `part of` directives. 60 | 2. Use adjacent strings to concatenate string literals. 61 | 3. Use collection literals when possible. 62 | 4. Use `whereType()` to filter a collection by type. 63 | 5. Test for `Future` when disambiguating a `FutureOr` whose type argument could be `Object`. 64 | 6. Follow a consistent rule for `var` and `final` on local variables. 65 | 7. Initialize fields at their declaration when possible. 66 | 8. Use initializing formals when possible. 67 | 9. Use `;` instead of `{}` for empty constructor bodies. 68 | 10. Use `rethrow` to rethrow a caught exception. 69 | 11. Override `hashCode` if you override `==`. 70 | 12. Make your `==` operator obey the mathematical rules of equality. 71 | 72 | ### Documentation 73 | 1. Format comments like sentences. 74 | 2. Use `///` doc comments to document members and types; don't use block comments for documentation. 75 | 3. Prefer writing doc comments for public APIs. 76 | 4. Consider writing doc comments for private APIs. 77 | 5. Consider including explanations of terminology, links, and references in library-level docs. 78 | 6. Start doc comments with a single-sentence summary. 79 | 7. Separate the first sentence of a doc comment into its own paragraph. 80 | 8. Use square brackets in doc comments to refer to in-scope identifiers. 81 | 9. Use prose to explain parameters, return values, and exceptions. 82 | 10. Put doc comments before metadata annotations. 83 | 11. Document why code exists or how it should be used, not just what it does. 84 | 85 | ### Testing 86 | 1. Write unit tests for business logic. 87 | 2. Write widget tests for UI components. 88 | 3. Aim for good test coverage. 89 | 90 | ### Widgets 91 | 1. Extract reusable widgets into separate components. 92 | 2. Use `StatelessWidget` when possible. 93 | 3. Keep build methods simple and focused. 94 | 95 | ### State Management 96 | 1. Choose appropriate state management based on complexity. 97 | 2. Avoid unnecessary `StatefulWidget`s. 98 | 3. Keep state as local as possible. 99 | 100 | ### Performance 101 | 1. Use `const` constructors when possible. 102 | 2. Avoid expensive operations in build methods. 103 | 3. Implement pagination for large lists. 104 | 105 | # Dart 3 Updates 106 | 107 | ### Branches 108 | 1. Use `if` statements for conditional branching. The condition must evaluate to a boolean. 109 | 2. `if` statements support optional `else` and `else if` clauses for multiple branches. 110 | 3. Use `if-case` statements to match and destructure a value against a single pattern. Example: `if (pair case [int x, int y]) { ... }` 111 | 4. If the pattern in an `if-case` matches, variables defined in the pattern are in scope for that branch. 112 | 5. If the pattern does not match in an `if-case`, control flows to the `else` branch if present. 113 | 6. Use `switch` statements to match a value against multiple patterns (cases). Each `case` can use any kind of pattern. 114 | 7. When a value matches a `case` pattern in a `switch` statement, the case body executes and control jumps to the end of the switch. `break` is not required. 115 | 8. You can end a non-empty `case` clause with `continue`, `throw`, or `return`. 116 | 9. Use `default` or `_` in a `switch` statement to handle unmatched values. 117 | 10. Empty `case` clauses fall through to the next case. Use `break` to prevent fallthrough. 118 | 11. Use `continue` with a label for non-sequential fallthrough between cases. 119 | 12. Use logical-or patterns (e.g., `case a || b`) to share a body or guard between cases. 120 | 13. Use `switch` expressions to produce a value based on matching cases. Syntax differs from statements: omit `case`, use `=>` for bodies, and separate cases with commas. 121 | 14. In `switch` expressions, the default case must use `_` (not `default`). 122 | 15. Dart checks for exhaustiveness in `switch` statements and expressions, reporting a compile-time error if not all possible values are handled. 123 | 16. To ensure exhaustiveness, use a default (`default` or `_`) case, or switch over enums or sealed types. 124 | 17. Use the `sealed` modifier on a class to enable exhaustiveness checking when switching over its subtypes. 125 | 18. Add a guard clause to a `case` using `when` to further constrain when a case matches. Example: `case pattern when condition:` 126 | 19. Guard clauses can be used in `if-case`, `switch` statements, and `switch` expressions. The guard is evaluated after pattern matching. 127 | 20. If a guard clause evaluates to false, execution proceeds to the next case (does not exit the switch). 128 | 129 | ### Patterns 130 | 1. Patterns are a syntactic category that represent the shape of values for matching and destructuring. 131 | 2. Pattern matching checks if a value has a certain shape, constant, equality, or type. 132 | 3. Pattern destructuring allows extracting parts of a matched value and binding them to variables. 133 | 4. Patterns can be nested, using subpatterns (outer/inner patterns) for recursive matching and destructuring. 134 | 5. Use wildcard patterns (`_`) to ignore parts of a matched value; use rest elements in list patterns to ignore remaining elements. 135 | 6. Patterns can be used in: 136 | - Local variable declarations and assignments 137 | - For and for-in loops 138 | - If-case and switch-case statements 139 | - Control flow in collection literals 140 | 7. Pattern variable declarations start with `var` or `final` and bind new variables from the matched value. Example: `var (a, [b, c]) = ('str', [1, 2]);` 141 | 8. Pattern variable assignments destructure a value and assign to existing variables. Example: `(b, a) = (a, b); // swap values` 142 | 9. Every case clause in `switch` and `if-case` contains a pattern. Any kind of pattern can be used in a case. 143 | 10. Case patterns are refutable; if the pattern doesn't match, execution continues to the next case. 144 | 11. Destructured values in a case become local variables scoped to the case body. 145 | 12. Use logical-or patterns (e.g., `case a || b`) to match multiple alternatives in a single case. 146 | 13. Use logical-or patterns with guards (`when`) to share a body or guard between cases. 147 | 14. Guard clauses (`when`) evaluate a condition after matching; if false, execution proceeds to the next case. 148 | 15. Patterns can be used in for and for-in loops to destructure collection elements (e.g., destructuring `MapEntry` in map iteration). 149 | 16. Object patterns match named object types and destructure their data using getters. Example: `var Foo(:one, :two) = myFoo;` 150 | 8. Constant patterns match if the value is equal to a constant (number, string, bool, named constant, const constructor, const collection, etc.). Use parentheses and `const` for complex expressions. 151 | 9. Variable patterns (`var name`, `final Type name`) bind new variables to matched/destructured values. Typed variable patterns only match if the value has the declared type. 152 | 10. Identifier patterns (`foo`, `_`) act as variable or constant patterns depending on context. `_` always acts as a wildcard and matches/discards any value. 153 | 11. Parenthesized patterns (`(subpattern)`) control pattern precedence and grouping, similar to expressions. 154 | 12. List patterns (`[subpattern1, subpattern2]`) match lists and destructure elements by position. The pattern length must match the list unless a rest element is used. 155 | 13. Rest elements (`...`, `...rest`) in list patterns match arbitrary-length lists or collect unmatched elements into a new list. 156 | 14. Map patterns (`{"key": subpattern}`) match maps and destructure by key. Only specified keys are matched; missing keys throw a `StateError`. 157 | 15. Record patterns (`(subpattern1, subpattern2)`, `(x: subpattern1, y: subpattern2)`) match records by shape and destructure positional/named fields. Field names can be omitted if inferred from variable or identifier patterns. 158 | 16. Object patterns (`ClassName(field1: subpattern1, field2: subpattern2)`) match objects by type and destructure using getters. Extra fields in the object are ignored. 159 | 17. Wildcard patterns (`_`, `Type _`) match any value without binding. Useful for ignoring values or type-checking without binding. 160 | 18. All pattern types can be nested and combined for expressive and precise matching and destructuring. 161 | 162 | ### Records 163 | 1. Records are anonymous, immutable, aggregate types that bundle multiple objects into a single value. 164 | 2. Records are fixed-sized, heterogeneous, and strongly typed. Each field can have a different type. 165 | 3. Records are real values: store them in variables, nest them, pass to/from functions, and use in lists, maps, and sets. 166 | 4. Record expressions use parentheses with comma-delimited positional and/or named fields, e.g. `('first', a: 2, b: true, 'last')`. 167 | 5. Record type annotations use parentheses with comma-delimited types. Named fields use curly braces: `({int a, bool b})`. 168 | 6. The names of named fields are part of the record's type (shape). Records with different named field names have different types. 169 | 7. Positional field names in type annotations are for documentation only and do not affect the record's type. 170 | 8. Record fields are accessed via built-in getters: positional fields as `$1`, `$2`, etc., and named fields by their name (e.g., `.a`). 171 | 9. Records are immutable: fields do not have setters. 172 | 10. Records are structurally typed: the set, types, and names of fields define the record's type (shape). 173 | 11. Two records are equal if they have the same shape and all corresponding field values are equal. Named field order does not affect equality. 174 | 12. Records automatically define `hashCode` and `==` based on structure and field values. 175 | 13. Use records for functions that return multiple values; destructure with pattern matching: `var (name, age) = userInfo(json);` 176 | 14. Destructure named fields with the colon syntax: `final (:name, :age) = userInfo(json);` 177 | 15. Using records for multiple returns is more concise and type-safe than using classes, lists, or maps. 178 | 16. Use lists of records for simple data tuples with the same shape. 179 | 17. Use type aliases (`typedef`) for record types to improve readability and maintainability. 180 | 18. Changing a record type alias does not guarantee all code using it is still type-safe; only classes provide full abstraction/encapsulation. 181 | 19. Extension types can wrap records but do not provide full abstraction or protection. 182 | 20. Records are best for simple, immutable data aggregation; use classes for abstraction, encapsulation, and behavior. 183 | 184 | # Common Flutter Errors 185 | 186 | 1. If you get a "RenderFlex overflowed" error, check if a `Row` or `Column` contains unconstrained widgets. Fix by wrapping children in `Flexible`, `Expanded`, or by setting constraints. 187 | 2. If you get "Vertical viewport was given unbounded height", ensure `ListView` or similar scrollable widgets inside a `Column` have a bounded height (e.g., wrap with `Expanded` or `SizedBox`). 188 | 3. If you get "An InputDecorator...cannot have an unbounded width", constrain the width of widgets like `TextField` using `Expanded`, `SizedBox`, or by placing them in a parent with width constraints. 189 | 4. If you get a "setState called during build" error, do not call `setState` or `showDialog` directly inside the build method. Trigger dialogs or state changes in response to user actions or after the build completes (e.g., using `addPostFrameCallback`). 190 | 5. If you get "The ScrollController is attached to multiple scroll views", make sure each `ScrollController` is only attached to a single scrollable widget at a time. 191 | 6. If you get a "RenderBox was not laid out" error, check for missing or unbounded constraints in your widget tree. This is often caused by using widgets like `ListView` or `Column` without proper size constraints. 192 | 7. Use the Flutter Inspector and review widget constraints to debug layout issues. Refer to the official documentation on constraints if needed. 193 | ### Provider Rules 194 | 195 | 1. Use `Provider`, `ChangeNotifierProvider`, `FutureProvider`, and `StreamProvider` to expose values and manage state in the widget tree. 196 | 2. Always specify the generic type when using `Provider`, `Consumer`, `context.watch`, `context.read`, or `context.select` for type safety. 197 | ```dart 198 | final value = context.watch(); 199 | ``` 200 | 3. Use `ChangeNotifierProvider` to automatically dispose of the model when it is no longer needed. 201 | ```dart 202 | ChangeNotifierProvider( 203 | create: (_) => MyNotifier(), 204 | child: MyApp(), 205 | ) 206 | ``` 207 | 4. For objects that depend on other providers or values that may change, use `ProxyProvider` or `ChangeNotifierProxyProvider` instead of creating the object from variables that can change over time. 208 | ```dart 209 | ProxyProvider0( 210 | update: (_, __) => MyModel(count), 211 | child: ... 212 | ) 213 | ``` 214 | 5. Use `MultiProvider` to group multiple providers and avoid deeply nested provider trees. 215 | ```dart 216 | MultiProvider( 217 | providers: [ 218 | Provider(create: (_) => Something()), 219 | Provider(create: (_) => SomethingElse()), 220 | ], 221 | child: someWidget, 222 | ) 223 | ``` 224 | 6. Use `context.watch()` to listen to changes and rebuild the widget when `T` changes. 225 | 7. Use `context.read()` to access a provider without listening for changes (e.g., in callbacks). 226 | 8. Use `context.select(R selector(T value))` to listen to only a small part of `T` and optimize rebuilds. 227 | ```dart 228 | final selected = context.select((model) => model.count); 229 | ``` 230 | 9. Use `Consumer` or `Selector` widgets for fine-grained rebuilds when you cannot access a descendant `BuildContext`. 231 | ```dart 232 | Consumer( 233 | builder: (context, value, child) => Text('$value'), 234 | ) 235 | ``` 236 | 10. To migrate from `ValueListenableProvider`, use `Provider` with `ValueListenableBuilder`. 237 | ```dart 238 | ValueListenableBuilder( 239 | valueListenable: myValueListenable, 240 | builder: (context, value, _) { 241 | return Provider.value( 242 | value: value, 243 | child: MyApp(), 244 | ); 245 | } 246 | ) 247 | ``` 248 | 11. Do not create your provider’s object from variables that can change over time; otherwise, the object will not update when the value changes. 249 | 12. For debugging, implement `toString` or use `DiagnosticableTreeMixin` to improve how your objects appear in Flutter DevTools. 250 | ```dart 251 | class MyClass with DiagnosticableTreeMixin { 252 | // ... 253 | @override 254 | String toString() => '$runtimeType(a: $a, b: $b)'; 255 | } 256 | ``` 257 | 13. Do not attempt to obtain providers inside `initState` or `constructor`; use them in `build`, callbacks, or lifecycle methods where the widget is fully mounted. 258 | 14. You can use any object as state, not just `ChangeNotifier`; use `Provider.value()` with a `StatefulWidget` if needed. 259 | 15. If you have a very large number of providers (e.g., 150+), consider mounting them over time (e.g., during splash screen animation) or avoid `MultiProvider` to prevent StackOverflowError. 260 | 261 | 262 | -------------------------------------------------------------------------------- /combined/flutter_dart_provider__under_6K.md: -------------------------------------------------------------------------------- 1 | # Flutter with Provider Rules 2 | 3 | ## Core Dart Principles 4 | 1. Use class modifiers to control if your class can be extended or used as an interface. 5 | 2. Make your `==` operator obey the mathematical rules of equality and override `hashCode` if you override `==`. 6 | 3. Type annotate fields, variables, and parameters when the type isn't obvious. 7 | 4. Use `Future` as the return type of asynchronous members that do not produce values. 8 | 5. Use getters for operations that conceptually access properties and setters for operations that conceptually change properties. 9 | 6. Use collection literals when possible. 10 | 7. Use `whereType()` to filter a collection by type. 11 | 8. Initialize fields at their declaration when possible. 12 | 9. Use initializing formals when possible. 13 | 10. Use `rethrow` to rethrow a caught exception. 14 | 15 | ## Flutter Best Practices 16 | 1. Extract reusable widgets into separate components. 17 | 2. Use `StatelessWidget` when possible. 18 | 3. Keep build methods simple and focused. 19 | 4. Avoid unnecessary `StatefulWidget`s. 20 | 5. Keep state as local as possible. 21 | 6. Use `const` constructors when possible. 22 | 7. Avoid expensive operations in build methods. 23 | 8. Implement pagination for large lists. 24 | 25 | ## Provider State Management 26 | 1. Use `Provider`, `ChangeNotifierProvider`, `FutureProvider`, and `StreamProvider` to expose values and manage state in the widget tree. 27 | 2. Always specify the generic type when using `Provider`, `Consumer`, `context.watch`, `context.read`, or `context.select` for type safety. 28 | ```dart 29 | final value = context.watch(); 30 | ``` 31 | 3. Use `ChangeNotifierProvider` to automatically dispose of the model when it is no longer needed. 32 | ```dart 33 | ChangeNotifierProvider( 34 | create: (_) => MyNotifier(), 35 | child: MyApp(), 36 | ) 37 | ``` 38 | 4. For objects that depend on other providers or values that may change, use `ProxyProvider` or `ChangeNotifierProxyProvider` instead of creating the object from variables that can change over time. 39 | ```dart 40 | ProxyProvider0( 41 | update: (_, __) => MyModel(count), 42 | child: ... 43 | ) 44 | ``` 45 | 5. Use `MultiProvider` to group multiple providers and avoid deeply nested provider trees. 46 | ```dart 47 | MultiProvider( 48 | providers: [ 49 | Provider(create: (_) => Something()), 50 | Provider(create: (_) => SomethingElse()), 51 | ], 52 | child: someWidget, 53 | ) 54 | ``` 55 | 6. Use `context.watch()` to listen to changes and rebuild the widget when `T` changes. 56 | 7. Use `context.read()` to access a provider without listening for changes (e.g., in callbacks). 57 | 8. Use `context.select(R selector(T value))` to listen to only a small part of `T` and optimize rebuilds. 58 | ```dart 59 | final selected = context.select((model) => model.count); 60 | ``` 61 | 9. Use `Consumer` or `Selector` widgets for fine-grained rebuilds when you cannot access a descendant `BuildContext`. 62 | ```dart 63 | Consumer( 64 | builder: (context, value, child) => Text('$value'), 65 | ) 66 | ``` 67 | 10. To migrate from `ValueListenableProvider`, use `Provider` with `ValueListenableBuilder`. 68 | ```dart 69 | ValueListenableBuilder( 70 | valueListenable: myValueListenable, 71 | builder: (context, value, _) { 72 | return Provider.value( 73 | value: value, 74 | child: MyApp(), 75 | ); 76 | } 77 | ) 78 | ``` 79 | 11. Do not create your provider's object from variables that can change over time; otherwise, the object will not update when the value changes. 80 | 12. For debugging, implement `toString` or use `DiagnosticableTreeMixin` to improve how your objects appear in Flutter DevTools. 81 | ```dart 82 | class MyClass with DiagnosticableTreeMixin { 83 | // ... 84 | @override 85 | String toString() => '$runtimeType(a: $a, b: $b)'; 86 | } 87 | ``` 88 | 13. Do not attempt to obtain providers inside `initState` or `constructor`; use them in `build`, callbacks, or lifecycle methods where the widget is fully mounted. 89 | 14. You can use any object as state, not just `ChangeNotifier`; use `Provider.value()` with a `StatefulWidget` if needed. 90 | 15. If you have a very large number of providers (e.g., 150+), consider mounting them over time (e.g., during splash screen animation) or avoid `MultiProvider` to prevent StackOverflowError. 91 | 92 | ## Dart 3 Modern Features 93 | 1. Use records for grouping values: `var user = ('John', age: 30)`. 94 | 2. Access record fields by position (`$1`) or name (`.age`). 95 | 3. Use patterns to destructure records, lists, and objects: `var (name, age) = user;` 96 | 4. Use switch expressions and pattern matching for concise, exhaustive control flow. 97 | 5. Use sealed classes for exhaustive `switch` and type safety. 98 | 99 | ## Common Flutter Errors 100 | 1. If you get a "RenderFlex overflowed" error, check if a `Row` or `Column` contains unconstrained widgets. Fix by wrapping children in `Flexible`, `Expanded`, or by setting constraints. 101 | 2. If you get "Vertical viewport was given unbounded height", ensure `ListView` or similar scrollable widgets inside a `Column` have a bounded height (e.g., wrap with `Expanded` or `SizedBox`). 102 | 3. If you get "An InputDecorator...cannot have an unbounded width", constrain the width of widgets like `TextField` using `Expanded`, `SizedBox`, or by placing them in a parent with width constraints. 103 | 4. If you get a "setState called during build" error, do not call `setState` or `showDialog` directly inside the build method. Trigger dialogs or state changes in response to user actions or after the build completes (e.g., using `addPostFrameCallback`). 104 | 5. If you get "The ScrollController is attached to multiple scroll views", make sure each `ScrollController` is only attached to a single scrollable widget at a time. 105 | 6. If you get a "RenderBox was not laid out" error, check for missing or unbounded constraints in your widget tree. This is often caused by using widgets like `ListView` or `Column` without proper size constraints. 106 | -------------------------------------------------------------------------------- /combined/flutter_dart_riverpod_mockito__under_6K.md: -------------------------------------------------------------------------------- 1 | # Flutter with Riverpod and Mockito Rules 2 | 3 | ## Core Dart Principles 4 | 1. Use class modifiers (`base`, `final`, `sealed`, `interface`) to control class extension. 5 | 2. Override `hashCode` if you override `==`. 6 | 3. Type annotate fields, variables, and parameters when the type isn't obvious. 7 | 4. Use `Future` for asynchronous members without return values. 8 | 5. Use getters for property access and setters for property changes. 9 | 10 | ## Riverpod Fundamentals 11 | 1. Always wrap your app with `ProviderScope` at the root (directly in `runApp`). 12 | 2. Define provider variables as `final` and at the top level (global scope). 13 | 3. Use `Provider` for synchronous values that don't change. 14 | 4. Use `StateProvider` for simple state that can be modified from the UI. 15 | 5. Use `FutureProvider` for asynchronous operations like API calls. 16 | 6. Use `StreamProvider` for real-time data streams. 17 | 7. Use `NotifierProvider` for complex state logic with methods. 18 | 8. Use `AsyncNotifierProvider` for complex async state logic with methods. 19 | 9. Providers are lazy—network requests or logic inside a provider are only executed when the provider is first read. 20 | 21 | ## Ref Object Usage 22 | 1. Use `ref.watch` to reactively depend on other providers; the provider will rebuild when dependencies change. 23 | 2. When using `ref.watch` with asynchronous providers, use `.future` to await the value if you need the resolved result. 24 | 3. Use `ref.listen` to perform side effects when a provider changes. 25 | 4. Use `ref.read` only when you cannot use `ref.watch`, such as inside Notifier methods or event handlers. 26 | 5. Be cautious with `ref.read`, as providers not being listened to may destroy their state if not actively watched. 27 | 28 | ## State Management with Notifiers 29 | 1. Use Notifiers to expose methods for performing side effects and modifying provider state. 30 | 2. Expose public methods on Notifiers for UI to trigger state changes or side effects. 31 | 3. In UI event handlers (e.g., button `onPressed`), use `ref.read` to call Notifier methods. 32 | 4. After performing a side effect, update the UI state by: 33 | - Setting the new state directly if the server returns the updated data. 34 | - Calling `ref.invalidateSelf()` to refresh the provider and re-fetch data. 35 | - Manually updating the local cache if the server does not return the new state. 36 | 5. Always handle loading and error states in the UI when performing side effects. 37 | 6. Do not perform side effects directly inside provider constructors or build methods. 38 | 39 | ## Auto Dispose & State Disposal 40 | 1. By default, with code generation, provider state is destroyed when the provider stops being listened to. 41 | 2. Opt out of automatic disposal by setting `keepAlive: true` (codegen) or using `ref.keepAlive()` (manual). 42 | 3. When not using code generation, enable `.autoDispose` on providers to activate automatic disposal. 43 | 4. Always enable automatic disposal for providers that receive parameters to prevent memory leaks. 44 | 5. Use `ref.onDispose` to register cleanup logic that runs when provider state is destroyed. 45 | 46 | ## Passing Arguments to Providers 47 | 1. Use provider "families" to pass arguments to providers; add `.family` after the provider type. 48 | 2. When using code generation, add parameters directly to the annotated function (excluding `ref`). 49 | 3. Always enable `autoDispose` for providers that receive parameters to avoid memory leaks. 50 | 4. The equality (`==`) of provider parameters determines caching—ensure parameters have consistent equality. 51 | 52 | ## Testing with Riverpod 53 | 1. Always create a new `ProviderContainer` (unit tests) or `ProviderScope` (widget tests) for each test. 54 | 2. In unit tests, never share `ProviderContainer` instances between tests: 55 | ```dart 56 | final container = createContainer(); 57 | expect(container.read(provider), equals('some value')); 58 | ``` 59 | 3. In widget tests, always wrap your widget tree with `ProviderScope`: 60 | ```dart 61 | await tester.pumpWidget( 62 | const ProviderScope(child: YourWidgetYouWantToTest()), 63 | ); 64 | ``` 65 | 4. Obtain a `ProviderContainer` in widget tests using `ProviderScope.containerOf(BuildContext)`: 66 | ```dart 67 | final element = tester.element(find.byType(YourWidgetYouWantToTest)); 68 | final container = ProviderScope.containerOf(element); 69 | ``` 70 | 5. After obtaining the container, you can read or interact with providers as needed for assertions: 71 | ```dart 72 | expect(container.read(provider), 'some value'); 73 | ``` 74 | 6. Use the `overrides` parameter to inject mocks or fakes for providers in your tests. 75 | 7. Use `container.listen` to spy on changes in a provider for assertions. 76 | 77 | ## Mockito Best Practices 78 | 1. Use a `Fake` when you want a lightweight, custom implementation of a class for testing. 79 | 2. Use a `Mock` when you need to verify interactions (method calls, arguments, call counts). 80 | 3. Use `@GenerateMocks([YourClass])` or `@GenerateNiceMocks([MockSpec()])` to generate mock classes. 81 | 4. Run `flutter pub run build_runner build` after adding mock annotations to generate the mock files. 82 | 5. Create mock instances from generated classes (e.g., `var mock = MockCat();`). 83 | 6. Use `when(mock.method()).thenReturn(value)` to stub method calls, and `when(mock.method()).thenThrow(error)` to stub errors. 84 | 7. Use `thenAnswer` to calculate a response at call time: `when(mock.method()).thenAnswer((_) => value);`. 85 | 86 | ## Dart 3 Modern Features 87 | 1. Use records for grouping values: `var user = ('John', age: 30, isAdmin: true);`. 88 | 2. Access record fields by position (`$1`, `$2`, ...) or by name: `user.$1`, `user.age`. 89 | 3. Use patterns to destructure records, lists, and objects: `var (name, :age) = user;`. 90 | 91 | ## Riverpod and Mockito Integration 92 | 1. When testing Riverpod providers, use Mockito to mock dependencies. 93 | -------------------------------------------------------------------------------- /combined/flutter_with_bloc__under_6K.md: -------------------------------------------------------------------------------- 1 | # Flutter with Bloc Rules 2 | 3 | ## Core Dart Principles 4 | 1. Use class modifiers (`base`, `final`, `sealed`, `interface`) to control class extension. 5 | 2. Override `hashCode` if you override `==`. 6 | 3. Type annotate fields, variables, and parameters when the type isn't obvious. 7 | 4. Use `Future` for asynchronous members without return values. 8 | 5. Use getters for property access and setters for property changes. 9 | 6. Use collection literals when possible. 10 | 7. Use `whereType()` to filter collections by type. 11 | 8. Initialize fields at declaration when possible. 12 | 9. Use initializing formals when possible. 13 | 10. Use `rethrow` to rethrow caught exceptions. 14 | 15 | ## Flutter Best Practices 16 | 1. Separate app into UI Layer (presentation) and Data Layer (business logic). 17 | 2. Keep views focused on presentation and extract reusable widgets. 18 | 3. Use StatelessWidget when possible and avoid unnecessary StatefulWidgets. 19 | 4. Keep build methods simple and focused. 20 | 5. Keep state as local as possible. 21 | 6. Use const constructors when possible. 22 | 7. Avoid expensive operations in build methods. 23 | 8. Implement pagination for large lists. 24 | 9. Keep files focused on a single responsibility. 25 | 10. Prefer making declarations private. 26 | 27 | ## Bloc Naming Conventions 28 | 1. Events should be named in the past tense, reflecting actions that have already occurred. Use the format: `BlocSubject` + optional noun + verb (event). For initial load events, use `BlocSubjectStarted`. 29 | 2. States should be nouns. For subclasses, use `BlocSubject` + `Initial` | `Success` | `Failure` | `InProgress`. For a single class, use `BlocSubjectState` with a `Status` enum (`initial`, `success`, `failure`, `loading`). 30 | 3. Prefer sealed classes or subclasses for exclusive states. 31 | 4. State classes should extend `Equatable`, use `@immutable`, implement a `copyWith` method, and use `const` constructors where possible. Always pass all relevant properties to the `props` getter when using Equatable to ensure proper equality comparisons. 32 | 5. Use a single concrete class with a status enum for non-exclusive states or when properties are frequently shared, but be aware this can lead to bloated and less type-safe code. 33 | 34 | ## Bloc Architecture 35 | 1. Implement a custom `BlocObserver` to monitor all state changes and errors across blocs for logging, analytics, or debugging. Override `onChange` and `onError` for global or per-bloc error handling. 36 | 2. Keep business logic inside blocs/cubits, not in UI widgets. 37 | 3. Only emit new states when the data actually changes to avoid unnecessary rebuilds. 38 | 4. Override `onError` in both `Cubit` and `BlocObserver` for robust error management. 39 | 5. Use `BlocProvider` to provide blocs to widget subtrees. 40 | 6. Use `BlocBuilder` to rebuild widgets in response to state changes. 41 | 7. Use `context.select` for fine-grained rebuilds, but avoid using it at the root of a widget tree, as it will cause the entire widget to rebuild on every state change. 42 | 8. Use `context.read()` for accessing a bloc instance without listening for changes (e.g., in callbacks), but avoid using it in `build` methods for reading state, as it won't trigger rebuilds. 43 | 9. Do not mutate state directly; always emit a new state. 44 | 10. Do not perform side effects inside blocs; use the UI layer or listen to state changes for side effects. 45 | 11. Separate your features into three layers: Presentation (UI), Business Logic (blocs/cubits), and Data (repositories/providers). 46 | 47 | ## Bloc Implementation 48 | 1. Use `Cubit` for simple state management without events; use `Bloc` for complex, event-driven state management. 49 | 2. Define the initial state by passing it to the superclass. 50 | 3. Only use the `emit` method inside a `Cubit` or `Bloc`. 51 | 4. UI components should listen to state changes and update only in response to new states. 52 | 5. Duplicate states are ignored; no state change will occur. 53 | 6. Use event transformers in `Bloc` for advanced event processing. 54 | 7. Prefer `Cubit` for simplicity and less boilerplate; prefer `Bloc` for traceability and advanced event handling. 55 | 56 | ## Flutter Bloc Integration 57 | 1. Use `BlocBuilder` to rebuild widgets in response to bloc or cubit state changes. 58 | 2. Use `BlocListener` to perform side effects in response to state changes. 59 | 3. Use `BlocConsumer` when you need both `BlocBuilder` and `BlocListener` functionality. 60 | 4. Use `BlocProvider` to provide blocs to widget subtrees via dependency injection. 61 | 5. Use `MultiBlocProvider` to provide multiple blocs and avoid deeply nested providers. 62 | 63 | ## Dart 3 Modern Features 64 | 1. Use records for grouping values: `var user = ('John', age: 30, isAdmin: true);`. Records are anonymous, immutable, aggregate types that bundle multiple objects into a single value. 65 | 2. Access record fields by position (`$1`, `$2`, ...) or by name: `user.$1`, `user.age`. Records automatically define `hashCode` and `==` based on structure and field values. 66 | 3. Use patterns to destructure records, lists, and objects. Example: `var (name, :age) = user;` or `if (response case {'user': {'name': String name}}) { print('User $name'); }`. 67 | 4. Use switch expressions and pattern matching for concise, exhaustive control flow. 68 | 5. Use sealed classes for exhaustive `switch` and type safety. 69 | -------------------------------------------------------------------------------- /combined/flutter_with_riverpod__under_6K.md: -------------------------------------------------------------------------------- 1 | # Flutter with Riverpod Rules 2 | 3 | ## Core Dart Principles 4 | 1. Use class modifiers (`base`, `final`, `sealed`, `interface`) to control class extension. 5 | 2. Override `hashCode` if you override `==`. 6 | 3. Type annotate fields, variables, and parameters when the type isn't obvious. 7 | 4. Use `Future` for asynchronous members without return values. 8 | 5. Use getters for property access and setters for property changes. 9 | 6. Use collection literals when possible. 10 | 7. Use `whereType()` to filter collections by type. 11 | 8. Initialize fields at declaration when possible. 12 | 9. Use initializing formals when possible. 13 | 10. Use `rethrow` to rethrow caught exceptions. 14 | 15 | ## Riverpod Fundamentals 16 | 1. Always wrap your app with `ProviderScope` at the root (directly in `runApp`). 17 | 2. Define provider variables as `final` and at the top level (global scope). 18 | 3. Use `Consumer` or `ConsumerWidget` in your UI to access providers via a `ref` object. 19 | 4. Use `Provider` for synchronous values that don't change. 20 | 5. Use `StateProvider` for simple state that can be modified from the UI. 21 | 6. Use `FutureProvider` for asynchronous operations like API calls. 22 | 7. Use `StreamProvider` for real-time data streams. 23 | 8. Use `NotifierProvider` for complex state logic with methods. 24 | 9. Use `AsyncNotifierProvider` for complex async state logic with methods. 25 | 10. Always install and use `riverpod_lint` to enable IDE refactoring and enforce best practices. 26 | 11. Providers are lazy—network requests or logic inside a provider are only executed when the provider is first read. 27 | 12. Multiple widgets can listen to the same provider; the provider will only execute once and cache the result. 28 | 13. Obtain the `Ref` object as a parameter in provider functions (or `WidgetRef` in widgets) to access other providers and manage lifecycles. 29 | 30 | **Example: Using @riverpod annotation** 31 | ```dart 32 | @riverpod 33 | int example(ref) { 34 | return 0; 35 | } 36 | ``` 37 | 38 | **Example: Using WidgetRef in a widget** 39 | ```dart 40 | class MyWidget extends ConsumerWidget { 41 | @override 42 | Widget build(BuildContext context, WidgetRef ref) { 43 | final value = ref.watch(myProvider); 44 | return Text('$value'); 45 | } 46 | } 47 | ``` 48 | 49 | ## Ref Object Usage 50 | 1. Use `ref.watch` to reactively depend on other providers; the provider will rebuild when dependencies change. 51 | 2. When using `ref.watch` with asynchronous providers, use `.future` to await the value if you need the resolved result. 52 | 3. Use `ref.listen` to perform side effects when a provider changes. 53 | 4. Use `ref.read` only when you cannot use `ref.watch`, such as inside Notifier methods or event handlers. 54 | 5. Be cautious with `ref.read`, as providers not being listened to may destroy their state if not actively watched. 55 | 56 | ## State Management with Notifiers 57 | 1. Use Notifiers to expose methods for performing side effects and modifying provider state. 58 | 2. Expose public methods on Notifiers for UI to trigger state changes or side effects. 59 | 3. In UI event handlers (e.g., button `onPressed`), use `ref.read` to call Notifier methods. 60 | 4. After performing a side effect, update the UI state by: 61 | - Setting the new state directly if the server returns the updated data. 62 | - Calling `ref.invalidateSelf()` to refresh the provider and re-fetch data. 63 | - Manually updating the local cache if the server does not return the new state. 64 | 5. Always handle loading and error states in the UI when performing side effects. 65 | 66 | ## Auto Dispose & State Disposal 67 | 1. By default, with code generation, provider state is destroyed when the provider stops being listened to. 68 | 2. Opt out of automatic disposal by setting `keepAlive: true` (codegen) or using `ref.keepAlive()` (manual). 69 | 3. When not using code generation, enable `.autoDispose` on providers to activate automatic disposal. 70 | 4. Always enable automatic disposal for providers that receive parameters to prevent memory leaks. 71 | 5. Use `ref.onDispose` to register cleanup logic that runs when provider state is destroyed. 72 | 73 | ## Passing Arguments to Providers 74 | 1. Use provider "families" to pass arguments to providers; add `.family` after the provider type. 75 | 2. When using code generation, add parameters directly to the annotated function (excluding `ref`). 76 | 3. Always enable `autoDispose` for providers that receive parameters to avoid memory leaks. 77 | 4. The equality (`==`) of provider parameters determines caching—ensure parameters have consistent equality. 78 | 5. Avoid passing objects that do not override `==` (such as plain `List` or `Map`) as provider parameters. 79 | 80 | ## Testing Providers 81 | 1. Always create a new `ProviderContainer` (unit tests) or `ProviderScope` (widget tests) for each test. 82 | 2. In unit tests, never share `ProviderContainer` instances between tests: 83 | ```dart 84 | final container = createContainer(); 85 | expect(container.read(provider), equals('some value')); 86 | ``` 87 | 3. In widget tests, always wrap your widget tree with `ProviderScope`: 88 | ```dart 89 | await tester.pumpWidget( 90 | const ProviderScope(child: YourWidgetYouWantToTest()), 91 | ); 92 | ``` 93 | 4. Obtain a `ProviderContainer` in widget tests using `ProviderScope.containerOf(BuildContext)`: 94 | ```dart 95 | final element = tester.element(find.byType(YourWidgetYouWantToTest)); 96 | final container = ProviderScope.containerOf(element); 97 | expect(container.read(provider), 'some value'); 98 | ``` 99 | 5. Prefer mocking dependencies (such as repositories) used by Notifiers rather than mocking Notifiers directly. 100 | 101 | ## Dart 3 Modern Features 102 | 1. Use records for grouping values: `var user = ('John', age: 30, isAdmin: true);`. 103 | 2. Access record fields by position (`$1`, `$2`, ...) or by name: `user.$1`, `user.age`. 104 | 3. Use patterns to destructure records, lists, and objects: `var (name, :age) = user;`. 105 | 4. Use switch expressions and pattern matching for concise, exhaustive control flow. 106 | 5. Use sealed classes for exhaustive `switch` and type safety. 107 | -------------------------------------------------------------------------------- /media/flutter_ai_rules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evanca/flutter-ai-rules/e3bda5c129802e3a942ed630dac353f4a8ab1767/media/flutter_ai_rules.png -------------------------------------------------------------------------------- /media/mocktail_md_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evanca/flutter-ai-rules/e3bda5c129802e3a942ed630dac353f4a8ab1767/media/mocktail_md_01.png -------------------------------------------------------------------------------- /media/mocktail_md_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evanca/flutter-ai-rules/e3bda5c129802e3a942ed630dac353f4a8ab1767/media/mocktail_md_02.png -------------------------------------------------------------------------------- /rules/bloc.md: -------------------------------------------------------------------------------- 1 | # Bloc Rules 2 | 3 | ### Naming Conventions 4 | 1. Name events in the past tense, as they represent actions that have already occurred from the bloc's perspective. 5 | 2. Use the format: `BlocSubject` + optional noun + verb (event). Example: `LoginButtonPressed`, `UserProfileLoaded` 6 | 3. For initial load events, use: `BlocSubjectStarted`. Example: `AuthenticationStarted` 7 | 4. The base event class should be named: `BlocSubjectEvent`. 8 | 5. Name states as nouns, since a state is a snapshot at a particular point in time. 9 | 6. When using subclasses for states, use the format: `BlocSubject` + `Initial` | `Success` | `Failure` | `InProgress`. Example: `LoginInitial`, `LoginSuccess`, `LoginFailure`, `LoginInProgress` 10 | 7. For single-class states, use: `BlocSubjectState` with a `BlocSubjectStatus` enum (`initial`, `success`, `failure`, `loading`). Example: `LoginState` with `LoginStatus.initial` 11 | 8. The base state class should always be named: `BlocSubjectState`. 12 | 13 | ### Modeling State 14 | 1. Extend `Equatable` for all state classes to enable value equality. 15 | 2. Annotate state classes with `@immutable` to enforce immutability. 16 | 3. Implement a `copyWith` method in state classes for easy state updates. 17 | 4. Use `const` constructors for state classes when possible. 18 | 5. Use a single concrete class with a status enum for simple, non-exclusive states or when many properties are shared. 19 | 6. In the single-class approach, make properties nullable and handle them based on the current status. 20 | 7. Use a sealed class with subclasses for well-defined, exclusive states. 21 | 8. Store shared properties in the sealed base class; keep state-specific properties in subclasses. 22 | 9. Use exhaustive `switch` statements to handle all possible state subclasses. 23 | 10. Prefer the sealed class approach for type safety and exhaustiveness; prefer the single-class approach for conciseness and flexibility. 24 | 11. Always pass all relevant properties to the `props` getter when using Equatable in state classes. 25 | 12. When using Equatable, copy List or Map properties with `List.of` or `Map.of` to ensure value equality. 26 | 13. To retain previous data after an error, use a single state class with nullable data and error fields. 27 | 14. Emit a new instance of the state each time you want the UI to update; do not reuse the same instance. 28 | 29 | ### Bloc Concepts 30 | 1. Use `Cubit` for simple state management without events; use `Bloc` for more complex, event-driven state management. 31 | 2. Define the initial state by passing it to the superclass in both `Cubit` and `Bloc`. 32 | 3. Only use the `emit` method inside a `Cubit` or `Bloc`; do not call it externally. 33 | 4. UI components should listen to state changes and update only in response to new states. 34 | 5. Duplicate states (`state == nextState`) are ignored; no state change will occur. 35 | 6. Override `onChange` in `Cubit` or `Bloc` to observe all state changes. 36 | 7. Use a custom `BlocObserver` to observe all state changes and errors globally. 37 | 8. Override `onError` in both `Cubit`/`Bloc` and `BlocObserver` for error handling. 38 | 9. Add events to a `Bloc` in response to user actions or lifecycle events. 39 | 10. Use `onTransition` in `Bloc` to observe the full transition (event, current state, next state). 40 | 11. Use event transformers (e.g., debounce, throttle) in `Bloc` for advanced event processing. 41 | 12. Prefer `Cubit` for simplicity and less boilerplate; prefer `Bloc` for traceability and advanced event handling. 42 | 13. If unsure, start with `Cubit` and refactor to `Bloc` if needed as requirements grow. 43 | 14. Initialize `BlocObserver` in `main.dart` for debugging and logging. 44 | 15. Always keep business logic out of UI widgets; only interact with cubits/blocs via events or public methods. 45 | 16. Internal events in a bloc should be private and only used for real-time updates from repositories. 46 | 17. Use custom event transformers for internal events if needed. 47 | 18. When exposing public methods on a cubit, only use them to trigger state changes and return `void` or `Future`. 48 | 19. For blocs, avoid exposing custom public methods; trigger state changes by adding events. 49 | 20. When using `BlocProvider.of(context)`, call it within a child `BuildContext`, not the same context where the bloc was provided. 50 | 51 | ### Architecture 52 | 1. Separate your features into three layers: Presentation, Business Logic, and Data. 53 | 2. The Data Layer is responsible for retrieving and manipulating data from sources such as databases or network requests. 54 | 3. Structure the Data Layer into repositories (wrappers around data providers) and data providers (perform CRUD operations). 55 | 4. The Business Logic Layer responds to input from the presentation layer and communicates with repositories to build new states. 56 | 5. The Presentation Layer renders UI based on bloc states and handles user input and lifecycle events. 57 | 6. Inject repositories into blocs via constructors; blocs should not directly access data providers. 58 | 7. Avoid direct bloc-to-bloc communication to prevent tight coupling. 59 | 8. To coordinate between blocs, use BlocListener in the presentation layer to listen to one bloc and add events to another. 60 | 9. For shared data, inject the same repository into multiple blocs; let each bloc listen to repository streams independently. 61 | 10. Always strive for loose coupling between architectural layers and components. 62 | 11. Structure your project consistently and intentionally; there is no single right way. 63 | 64 | ### Flutter Bloc Concepts 65 | 1. Use `BlocBuilder` to rebuild widgets in response to bloc or cubit state changes; the builder function must be pure. 66 | 2. Use `BlocListener` to perform side effects (e.g., navigation, dialogs) in response to state changes. 67 | 3. Use `BlocConsumer` when you need both `BlocBuilder` and `BlocListener` functionality in a single widget. 68 | 4. Use `BlocProvider` to provide blocs to widget subtrees via dependency injection. 69 | 5. Use `MultiBlocProvider` to provide multiple blocs and avoid deeply nested providers. 70 | 6. Use `BlocSelector` to rebuild widgets only when a selected part of the state changes. 71 | 7. Use `MultiBlocListener` to listen for state changes and trigger side effects; avoid nesting listeners by using `MultiBlocListener`. 72 | 8. Use `RepositoryProvider` to provide repositories or services to the widget tree. 73 | 9. Use `MultiRepositoryProvider` to provide multiple repositories and avoid nesting. 74 | 10. Use `context.read()` to access a bloc or repository without listening for changes (e.g., in callbacks). 75 | 11. Use `context.watch()` inside the build method to listen for changes and trigger rebuilds. 76 | 12. Use `context.select()` to listen for changes in a specific part of a bloc’s state. 77 | 13. Avoid using `context.watch` or `context.select` at the root of the build method to prevent unnecessary rebuilds. 78 | 14. Prefer `BlocBuilder` and `BlocSelector` over `context.watch` and `context.select` for explicit rebuild scoping. 79 | 15. Scope rebuilds using `Builder` when using `context.watch` or `context.select` for multiple blocs. 80 | 16. Handle all possible cubit/bloc states explicitly in the UI (e.g., empty, loading, error, populated). 81 | 82 | ### Testing 83 | 1. Add the `test` and `bloc_test` packages to your dev dependencies for bloc testing. 84 | 2. Organize tests into groups to share setup and teardown logic. 85 | 3. Create a dedicated test file (e.g., `counter_bloc_test.dart`) for each bloc. 86 | 4. Import the `test` and `bloc_test` packages in your test files. 87 | 5. Use `setUp` to initialize bloc instances before each test and `tearDown` to clean up after tests. 88 | 6. Test the bloc’s initial state before testing transitions. 89 | 7. Use the `blocTest` function to test bloc state transitions in response to events. 90 | 8. Assert the expected sequence of emitted states for each bloc event. 91 | 9. Keep tests concise, focused, and easy to maintain to ensure confidence in refactoring. 92 | 10. Mock cubits/blocs in widget tests to verify UI behavior for all possible states. 93 | 94 | TOTAL CHAR COUNT: 7810 95 | -------------------------------------------------------------------------------- /rules/code_review.md: -------------------------------------------------------------------------------- 1 | # Code Review Ruleset for LLMs 2 | 3 | 1. Check that the current branch is a feature, bugfix, or PR/MR branch and not a main or develop branch. 4 | 2. Verify the branch is up-to-date with the target branch. 5 | 3. Identify the target branch for the merge and list all files that have changed, been added, or deleted. 6 | 4. For each changed file, check that the file is in the correct directory. 7 | 5. For each changed file, check that the file name follows naming conventions. 8 | 6. For each changed file, verify the file’s responsibility is clear and that the reason for its change or addition is understandable. 9 | 7. For each changed file, review the code for readability and ensure variable, function, and class names are descriptive and consistent. 10 | 8. For each changed file, check the logic and correctness of the code, ensuring there are no logic errors or missing edge cases. 11 | 9. For each changed file, check that the code is modular and does not contain unnecessary duplication (maintainability). 12 | 10. For each changed file, ensure errors and exceptions are handled appropriately. 13 | 11. For each changed file, check for potential security concerns such as input validation and secrets in code. 14 | 12. For each changed file, check for obvious performance issues or inefficiencies. 15 | 13. For each changed file, verify that public APIs, complex logic, and new modules are documented. 16 | 14. For each changed file, ensure there is sufficient test coverage for new or changed logic. 17 | 15. For each changed file, ensure the code matches the project’s style guide and coding patterns. 18 | 16. For generated files, confirm they are up-to-date and not manually edited. 19 | 17. Check that the overall change set is focused and scoped to the stated purpose and does not include unrelated or unnecessary changes. 20 | 18. Verify that the PR/MR description accurately reflects the changes made. 21 | 19. Ensure there are new or updated tests covering new or changed logic. 22 | 20. Ensure all tests pass in the continuous integration system. 23 | 21. Provide clear, constructive feedback for any issues found, including suggestions for improvement and requests for clarification if anything is unclear. 24 | 22. The expected output is an answer in the chat, mentioning conclusions and recommendations per file. -------------------------------------------------------------------------------- /rules/dart_3_updates.md: -------------------------------------------------------------------------------- 1 | # Dart 3 Updates 2 | 3 | ### Branches 4 | 1. Use `if` statements for conditional branching. The condition must evaluate to a boolean. 5 | 2. `if` statements support optional `else` and `else if` clauses for multiple branches. 6 | 3. Use `if-case` statements to match and destructure a value against a single pattern. Example: `if (pair case [int x, int y]) { ... }` 7 | 4. If the pattern in an `if-case` matches, variables defined in the pattern are in scope for that branch. 8 | 5. If the pattern does not match in an `if-case`, control flows to the `else` branch if present. 9 | 6. Use `switch` statements to match a value against multiple patterns (cases). Each `case` can use any kind of pattern. 10 | 7. When a value matches a `case` pattern in a `switch` statement, the case body executes and control jumps to the end of the switch. `break` is not required. 11 | 8. You can end a non-empty `case` clause with `continue`, `throw`, or `return`. 12 | 9. Use `default` or `_` in a `switch` statement to handle unmatched values. 13 | 10. Empty `case` clauses fall through to the next case. Use `break` to prevent fallthrough. 14 | 11. Use `continue` with a label for non-sequential fallthrough between cases. 15 | 12. Use logical-or patterns (e.g., `case a || b`) to share a body or guard between cases. 16 | 13. Use `switch` expressions to produce a value based on matching cases. Syntax differs from statements: omit `case`, use `=>` for bodies, and separate cases with commas. 17 | 14. In `switch` expressions, the default case must use `_` (not `default`). 18 | 15. Dart checks for exhaustiveness in `switch` statements and expressions, reporting a compile-time error if not all possible values are handled. 19 | 16. To ensure exhaustiveness, use a default (`default` or `_`) case, or switch over enums or sealed types. 20 | 17. Use the `sealed` modifier on a class to enable exhaustiveness checking when switching over its subtypes. 21 | 18. Add a guard clause to a `case` using `when` to further constrain when a case matches. Example: `case pattern when condition:` 22 | 19. Guard clauses can be used in `if-case`, `switch` statements, and `switch` expressions. The guard is evaluated after pattern matching. 23 | 20. If a guard clause evaluates to false, execution proceeds to the next case (does not exit the switch). 24 | 25 | ### Patterns 26 | 1. Patterns are a syntactic category that represent the shape of values for matching and destructuring. 27 | 2. Pattern matching checks if a value has a certain shape, constant, equality, or type. 28 | 3. Pattern destructuring allows extracting parts of a matched value and binding them to variables. 29 | 4. Patterns can be nested, using subpatterns (outer/inner patterns) for recursive matching and destructuring. 30 | 5. Use wildcard patterns (`_`) to ignore parts of a matched value; use rest elements in list patterns to ignore remaining elements. 31 | 6. Patterns can be used in: 32 | - Local variable declarations and assignments 33 | - For and for-in loops 34 | - If-case and switch-case statements 35 | - Control flow in collection literals 36 | 7. Pattern variable declarations start with `var` or `final` and bind new variables from the matched value. Example: `var (a, [b, c]) = ('str', [1, 2]);` 37 | 8. Pattern variable assignments destructure a value and assign to existing variables. Example: `(b, a) = (a, b); // swap values` 38 | 9. Every case clause in `switch` and `if-case` contains a pattern. Any kind of pattern can be used in a case. 39 | 10. Case patterns are refutable; if the pattern doesn't match, execution continues to the next case. 40 | 11. Destructured values in a case become local variables scoped to the case body. 41 | 12. Use logical-or patterns (e.g., `case a || b`) to match multiple alternatives in a single case. 42 | 13. Use logical-or patterns with guards (`when`) to share a body or guard between cases. 43 | 14. Guard clauses (`when`) evaluate a condition after matching; if false, execution proceeds to the next case. 44 | 15. Patterns can be used in for and for-in loops to destructure collection elements (e.g., destructuring `MapEntry` in map iteration). 45 | 16. Object patterns match named object types and destructure their data using getters. Example: `var Foo(:one, :two) = myFoo;` 46 | 17. Use patterns to destructure records, including positional and named fields, directly into local variables. 47 | 18. Patterns enable algebraic data type style code: use sealed classes and switch on subtypes for exhaustive matching. 48 | 19. Patterns simplify validation and destructuring of complex data structures, such as JSON, in a declarative way. Example: `if (data case {'user': [String name, int age]}) { ... }` 49 | 20. Patterns provide a concise alternative to verbose type-checking and destructuring code. 50 | 51 | ### Pattern Types 52 | 1. Pattern precedence determines evaluation order; use parentheses to group lower-precedence patterns. 53 | 2. Logical-or patterns (`pattern1 || pattern2`) match if any branch matches, evaluated left-to-right. All branches must bind the same set of variables. 54 | 3. Logical-and patterns (`pattern1 && pattern2`) match if both subpatterns match. Bound variable names must not overlap between subpatterns. 55 | 4. Relational patterns (`==`, `!=`, `<`, `>`, `<=`, `>=`) match if the value compares as specified to a constant. Useful for numeric ranges and can be combined with logical-and. 56 | 5. Cast patterns (`subpattern as Type`) assert and cast a value to a type before passing it to a subpattern. Throws if the value is not of the type. 57 | 6. Null-check patterns (`subpattern?`) match if the value is not null, then match the inner pattern. Binds the non-nullable type. Use constant pattern `null` to match null. 58 | 7. Null-assert patterns (`subpattern!`) match if the value is not null, else throw. Use in variable declarations to eliminate nulls. Use constant pattern `null` to match null. 59 | 8. Constant patterns match if the value is equal to a constant (number, string, bool, named constant, const constructor, const collection, etc.). Use parentheses and `const` for complex expressions. 60 | 9. Variable patterns (`var name`, `final Type name`) bind new variables to matched/destructured values. Typed variable patterns only match if the value has the declared type. 61 | 10. Identifier patterns (`foo`, `_`) act as variable or constant patterns depending on context. `_` always acts as a wildcard and matches/discards any value. 62 | 11. Parenthesized patterns (`(subpattern)`) control pattern precedence and grouping, similar to expressions. 63 | 12. List patterns (`[subpattern1, subpattern2]`) match lists and destructure elements by position. The pattern length must match the list unless a rest element is used. 64 | 13. Rest elements (`...`, `...rest`) in list patterns match arbitrary-length lists or collect unmatched elements into a new list. 65 | 14. Map patterns (`{"key": subpattern}`) match maps and destructure by key. Only specified keys are matched; missing keys throw a `StateError`. 66 | 15. Record patterns (`(subpattern1, subpattern2)`, `(x: subpattern1, y: subpattern2)`) match records by shape and destructure positional/named fields. Field names can be omitted if inferred from variable or identifier patterns. 67 | 16. Object patterns (`ClassName(field1: subpattern1, field2: subpattern2)`) match objects by type and destructure using getters. Extra fields in the object are ignored. 68 | 17. Wildcard patterns (`_`, `Type _`) match any value without binding. Useful for ignoring values or type-checking without binding. 69 | 18. All pattern types can be nested and combined for expressive and precise matching and destructuring. 70 | 71 | ### Records 72 | 1. Records are anonymous, immutable, aggregate types that bundle multiple objects into a single value. 73 | 2. Records are fixed-sized, heterogeneous, and strongly typed. Each field can have a different type. 74 | 3. Records are real values: store them in variables, nest them, pass to/from functions, and use in lists, maps, and sets. 75 | 4. Record expressions use parentheses with comma-delimited positional and/or named fields, e.g. `('first', a: 2, b: true, 'last')`. 76 | 5. Record type annotations use parentheses with comma-delimited types. Named fields use curly braces: `({int a, bool b})`. 77 | 6. The names of named fields are part of the record's type (shape). Records with different named field names have different types. 78 | 7. Positional field names in type annotations are for documentation only and do not affect the record's type. 79 | 8. Record fields are accessed via built-in getters: positional fields as `$1`, `$2`, etc., and named fields by their name (e.g., `.a`). 80 | 9. Records are immutable: fields do not have setters. 81 | 10. Records are structurally typed: the set, types, and names of fields define the record's type (shape). 82 | 11. Two records are equal if they have the same shape and all corresponding field values are equal. Named field order does not affect equality. 83 | 12. Records automatically define `hashCode` and `==` based on structure and field values. 84 | 13. Use records for functions that return multiple values; destructure with pattern matching: `var (name, age) = userInfo(json);` 85 | 14. Destructure named fields with the colon syntax: `final (:name, :age) = userInfo(json);` 86 | 15. Using records for multiple returns is more concise and type-safe than using classes, lists, or maps. 87 | 16. Use lists of records for simple data tuples with the same shape. 88 | 17. Use type aliases (`typedef`) for record types to improve readability and maintainability. 89 | 18. Changing a record type alias does not guarantee all code using it is still type-safe; only classes provide full abstraction/encapsulation. 90 | 19. Extension types can wrap records but do not provide full abstraction or protection. 91 | 20. Records are best for simple, immutable data aggregation; use classes for abstraction, encapsulation, and behavior. 92 | 93 | TOTAL CHAR COUNT: 9612 94 | -------------------------------------------------------------------------------- /rules/effective_dart.md: -------------------------------------------------------------------------------- 1 | # Effective Dart Rules 2 | 3 | ### Naming Conventions 4 | 1. Use terms consistently throughout your code. 5 | 2. Follow existing mnemonic conventions when naming type parameters (e.g., `E` for element, `K`/`V` for key/value, `T`/`S`/`U` for generic types). 6 | 3. Name types using `UpperCamelCase` (classes, enums, typedefs, type parameters). 7 | 4. Name extensions using `UpperCamelCase`. 8 | 5. Name packages, directories, and source files using `lowercase_with_underscores`. 9 | 6. Name import prefixes using `lowercase_with_underscores`. 10 | 7. Name other identifiers using `lowerCamelCase` (variables, parameters, named parameters). 11 | 8. Capitalize acronyms and abbreviations longer than two letters like words. 12 | 9. Avoid abbreviations unless the abbreviation is more common than the unabbreviated term. 13 | 10. Prefer putting the most descriptive noun last in names. 14 | 11. Consider making code read like a sentence when designing APIs. 15 | 12. Prefer a noun phrase for non-boolean properties or variables. 16 | 13. Prefer a non-imperative verb phrase for boolean properties or variables. 17 | 14. Prefer the positive form for boolean property and variable names. 18 | 15. Consider omitting the verb for named boolean parameters. 19 | 16. Use camelCase for variable and function names. 20 | 17. Use PascalCase for class names. 21 | 18. Use snake_case for file names. 22 | 23 | ### Types and Functions 24 | 1. Use class modifiers to control if your class can be extended or used as an interface. 25 | 2. Type annotate variables without initializers. 26 | 3. Type annotate fields and top-level variables if the type isn't obvious. 27 | 4. Annotate return types on function declarations. 28 | 5. Annotate parameter types on function declarations. 29 | 6. Write type arguments on generic invocations that aren't inferred. 30 | 7. Annotate with `dynamic` instead of letting inference fail. 31 | 8. Use `Future` as the return type of asynchronous members that do not produce values. 32 | 9. Use getters for operations that conceptually access properties. 33 | 10. Use setters for operations that conceptually change properties. 34 | 11. Use a function declaration to bind a function to a name. 35 | 12. Use inclusive start and exclusive end parameters to accept a range. 36 | 37 | ### Style 38 | 1. Format your code using `dart format`. 39 | 2. Use curly braces for all flow control statements. 40 | 3. Prefer `final` over `var` when variable values won't change. 41 | 4. Use `const` for compile-time constants. 42 | 43 | ### Imports & Files 44 | 1. Don't import libraries inside the `src` directory of another package. 45 | 2. Don't allow import paths to reach into or out of `lib`. 46 | 3. Prefer relative import paths within a package. 47 | 4. Don't use `/lib/` or `../` in import paths. 48 | 5. Consider writing a library-level doc comment for library files. 49 | 50 | ### Structure 51 | 1. Keep files focused on a single responsibility. 52 | 2. Limit file length to maintain readability. 53 | 3. Group related functionality together. 54 | 4. Prefer making fields and top-level variables `final`. 55 | 5. Consider making your constructor `const` if the class supports it. 56 | 6. Prefer making declarations private. 57 | 58 | ### Usage 59 | 1. Use strings in `part of` directives. 60 | 2. Use adjacent strings to concatenate string literals. 61 | 3. Use collection literals when possible. 62 | 4. Use `whereType()` to filter a collection by type. 63 | 5. Test for `Future` when disambiguating a `FutureOr` whose type argument could be `Object`. 64 | 6. Follow a consistent rule for `var` and `final` on local variables. 65 | 7. Initialize fields at their declaration when possible. 66 | 8. Use initializing formals when possible. 67 | 9. Use `;` instead of `{}` for empty constructor bodies. 68 | 10. Use `rethrow` to rethrow a caught exception. 69 | 11. Override `hashCode` if you override `==`. 70 | 12. Make your `==` operator obey the mathematical rules of equality. 71 | 72 | ### Documentation 73 | 1. Format comments like sentences. 74 | 2. Use `///` doc comments to document members and types; don't use block comments for documentation. 75 | 3. Prefer writing doc comments for public APIs. 76 | 4. Consider writing doc comments for private APIs. 77 | 5. Consider including explanations of terminology, links, and references in library-level docs. 78 | 6. Start doc comments with a single-sentence summary. 79 | 7. Separate the first sentence of a doc comment into its own paragraph. 80 | 8. Use square brackets in doc comments to refer to in-scope identifiers. 81 | 9. Use prose to explain parameters, return values, and exceptions. 82 | 10. Put doc comments before metadata annotations. 83 | 11. Document why code exists or how it should be used, not just what it does. 84 | 85 | ### Testing 86 | 1. Write unit tests for business logic. 87 | 2. Write widget tests for UI components. 88 | 3. Aim for good test coverage. 89 | 90 | ### Widgets 91 | 1. Extract reusable widgets into separate components. 92 | 2. Use `StatelessWidget` when possible. 93 | 3. Keep build methods simple and focused. 94 | 95 | ### State Management 96 | 1. Choose appropriate state management based on complexity. 97 | 2. Avoid unnecessary `StatefulWidget`s. 98 | 3. Keep state as local as possible. 99 | 100 | ### Performance 101 | 1. Use `const` constructors when possible. 102 | 2. Avoid expensive operations in build methods. 103 | 3. Implement pagination for large lists. 104 | 105 | TOTAL CHAR COUNT: 4993 106 | -------------------------------------------------------------------------------- /rules/firebase/cloud_firestore.md: -------------------------------------------------------------------------------- 1 | # Cloud Firestore Rules 2 | 3 | ### Database Selection 4 | 5 | 1. Choose Cloud Firestore for applications with rich data models requiring queryability, scalability, and high availability. 6 | 2. Use Cloud Firestore when your application needs complex, hierarchical data organization at scale using subcollections within documents. 7 | 3. Consider Cloud Firestore for applications where availability is of utmost importance (typical uptime performance of 99.999%). 8 | 4. Choose Cloud Firestore if you need to chain filters and combine filtering and sorting on a property in a single query. 9 | 5. Select Cloud Firestore when you need transactions that can atomically read and write data from any part of the database. 10 | 6. Consider Realtime Database instead for applications with simple data models requiring simple lookups and extremely low-latency synchronization (typical response times under 10ms). 11 | 12 | ### Setup and Configuration 13 | 14 | 1. Install Cloud Firestore using `flutter pub add cloud_firestore` in your Flutter project. 15 | 2. Import the package in your Dart code using `import 'package:cloud_firestore/cloud_firestore.dart';`. 16 | 3. Initialize Firestore with `final db = FirebaseFirestore.instance;` after Firebase has been initialized. 17 | 4. Select the database location closest to your users and compute resources to minimize latency. 18 | 5. Use multi-region locations for critical applications to maximize availability and durability. 19 | 6. Use regional locations for lower costs and lower write latency for latency-sensitive applications. 20 | 7. For iOS & macOS, consider using pre-compiled frameworks to improve build times by modifying your Podfile. 21 | ``` 22 | pod 'FirebaseFirestore', 23 | :git => 'https://github.com/invertase/firestore-ios-sdk-frameworks.git', 24 | :tag => 'IOS_SDK_VERSION' 25 | ``` 26 | 27 | ### Document Structure 28 | 29 | 1. Avoid using document IDs `.` and `..` as they have special meaning in Firestore paths. 30 | 2. Avoid using forward slashes (`/`) in document IDs as they are used as path separators. 31 | 3. Do not use monotonically increasing document IDs (e.g., Customer1, Customer2, Customer3) as they can lead to hotspots that impact latency. 32 | 4. Use Firestore's automatic document IDs when possible to avoid hotspotting on writes. 33 | ```dart 34 | // Create a new document with a generated ID 35 | db.collection("users").add(user).then((DocumentReference doc) => 36 | print('DocumentSnapshot added with ID: ${doc.id}')); 37 | ``` 38 | 5. Avoid using the following characters in field names as they require extra escaping: `.` (period), `[` (left bracket), `]` (right bracket), `*` (asterisk), `` ` `` (backtick). 39 | 6. Organize complex, hierarchical data using subcollections within documents rather than deeply nested objects. 40 | 41 | ### Indexing 42 | 43 | 1. Set collection-level index exemptions to reduce write latency and lower storage costs. 44 | 2. Disable Descending & Array indexing for fields that don't need them to improve performance. 45 | 3. Exempt string fields that hold long values and aren't used for querying from indexing to reduce storage costs. 46 | 4. Exempt fields with sequential values (like timestamps) from indexing if not used in queries to avoid the 500 writes per second limit. 47 | 5. Add single-field exemptions for TTL (time-to-live) fields to improve performance at higher traffic rates. 48 | 6. Exempt large array or map fields from indexing if not used in queries to avoid approaching the 40,000 index entries per document limit. 49 | 7. Be aware that queries in Firestore are indexed by default, with performance proportional to the size of your result set, not your dataset. 50 | 51 | ### Read and Write Operations 52 | 53 | 1. Use asynchronous calls instead of synchronous calls to minimize latency impact. 54 | ```dart 55 | // Read data asynchronously 56 | await db.collection("users").get().then((event) { 57 | for (var doc in event.docs) { 58 | print("${doc.id} => ${doc.data()}"); 59 | } 60 | }); 61 | ``` 62 | 2. Do not use offsets for pagination; use cursors instead to avoid retrieving and billing for skipped documents. 63 | 3. Implement transaction retries when accessing Firestore directly through REST or RPC APIs. 64 | 4. Be aware of the limitations on the rate at which a single document can be updated. 65 | 5. For writing a large number of documents, consider using a bulk writer instead of the atomic batch writer. 66 | 6. When performing multiple independent operations (like a document lookup and a query), execute them in parallel rather than sequentially. 67 | 7. Remember that Firestore queries are shallow and only return documents in a particular collection or collection group, not subcollection data. 68 | 8. Be aware that Firestore has limits on write rates to individual documents (around 1 write per second per document) and indexes. 69 | 70 | ### Designing for Scale 71 | 72 | 1. Avoid high read or write rates to lexicographically close documents to prevent hotspotting. 73 | 2. Avoid creating new documents with monotonically increasing fields (like timestamps) at a very high rate. 74 | 3. Avoid deleting documents in a collection at a high rate. 75 | 4. Gradually increase traffic when writing to the database at a high rate. 76 | 5. Avoid queries that skip over recently deleted data by using the `start_at` method to find the best place to start. 77 | 6. For high-traffic applications, distribute writes across different document paths to avoid contention. 78 | 7. Be aware that Firestore scales automatically to around 1 million concurrent connections and 10,000 writes/second. 79 | 80 | ### Real-time Updates 81 | 82 | 1. Limit the number of simultaneous real-time listeners in your application. 83 | 2. Detach listeners when they are no longer needed to free up resources. 84 | ```dart 85 | // Set up a listener 86 | final subscription = db.collection("users") 87 | .snapshots() 88 | .listen((event) { 89 | // Handle the data 90 | }); 91 | 92 | // Later, when no longer needed: 93 | subscription.cancel(); 94 | ``` 95 | 3. Use compound queries to filter data on the server side rather than filtering in the client. 96 | 4. Consider using server-side filtering to reduce the amount of data transferred to clients. 97 | 5. For large collections, use queries to limit the data being listened to rather than listening to the entire collection. 98 | 6. For presence functionality (knowing when a client is online/offline), implement custom presence solutions as described in Firebase documentation. 99 | 100 | ### Security 101 | 102 | 1. Always use Firebase Security Rules to protect your Firestore data. 103 | 2. Validate user input before submitting it to Firestore to prevent injection attacks. 104 | 3. Use transactions for operations that require atomic updates to multiple documents. 105 | 4. Implement proper error handling for Firestore operations to handle potential failures gracefully. 106 | 5. Never store sensitive information in Firestore without proper access controls. 107 | 6. Be aware that Firestore security rules don't cascade unless you use a wildcard. 108 | 7. Remember that if a query's results might contain data the user doesn't have access to, the entire query fails. 109 | -------------------------------------------------------------------------------- /rules/firebase/cloud_functions.md: -------------------------------------------------------------------------------- 1 | # Firebase Cloud Functions Rules 2 | 3 | ### Setup and Configuration 4 | 5 | 1. Install the Cloud Functions plugin using `flutter pub add cloud_functions`. 6 | 2. Import the plugin in your Dart code using `import 'package:cloud_functions/cloud_functions.dart';`. 7 | 3. Initialize Firebase before using any Cloud Functions features. 8 | 4. Access the Cloud Functions instance using `final functions = FirebaseFunctions.instance;`. 9 | 5. For region-specific deployments, specify the region when getting the instance. 10 | 6. Deploy your callable functions to Firebase before attempting to call them from your Flutter app. 11 | 7. Consider implementing App Check to prevent abuse of your Cloud Functions. 12 | 13 | ### Calling Functions 14 | 15 | 1. Call a Cloud Function using the `httpsCallable` method followed by the `call` method. 16 | ```dart 17 | final result = await FirebaseFunctions.instance 18 | .httpsCallable('functionName') 19 | .call(data); 20 | ``` 21 | 2. Pass data to functions as a Map, which will be automatically serialized to JSON. 22 | ```dart 23 | final result = await FirebaseFunctions.instance 24 | .httpsCallable('addMessage') 25 | .call({ 26 | "text": messageText, 27 | "push": true, 28 | }); 29 | ``` 30 | 3. Access the function result data using the `data` property of the returned object. 31 | ```dart 32 | final responseData = result.data; 33 | ``` 34 | 4. Be aware that the data returned from a function is automatically deserialized from JSON. 35 | 5. Use type casting to convert the returned data to the expected type. 36 | ```dart 37 | final responseString = result.data as String; 38 | ``` 39 | 6. Avoid passing sensitive information like authentication tokens in function parameters, as they are automatically included by the SDK. 40 | 7. Keep function names consistent between your client code and server-side implementations. 41 | 42 | ### Error Handling 43 | 44 | 1. Use try-catch blocks to handle errors when calling Cloud Functions. 45 | ```dart 46 | try { 47 | final result = await FirebaseFunctions.instance 48 | .httpsCallable('functionName') 49 | .call(data); 50 | // Handle successful result 51 | } catch (e) { 52 | if (e is FirebaseFunctionsException) { 53 | // Handle specific function error 54 | print('Error code: ${e.code}'); 55 | print('Error message: ${e.message}'); 56 | print('Error details: ${e.details}'); 57 | } else { 58 | // Handle general error 59 | print('Error: $e'); 60 | } 61 | } 62 | ``` 63 | 2. Check for `FirebaseFunctionsException` to handle specific function errors. 64 | 3. Access error details using the `code`, `message`, and `details` properties of `FirebaseFunctionsException`. 65 | 4. Implement proper error handling for network connectivity issues. 66 | 5. Handle timeouts appropriately, especially for long-running functions. 67 | 6. Provide meaningful error messages to users when function calls fail. 68 | 7. Consider implementing retry logic for transient errors. 69 | 70 | ### Performance Optimization 71 | 72 | 1. Set appropriate timeouts for function calls based on expected execution time. 73 | ```dart 74 | final functions = FirebaseFunctions.instance; 75 | functions.useFunctionsEmulator('localhost', 5001); 76 | final callable = functions.httpsCallable( 77 | 'functionName', 78 | options: HttpsCallableOptions( 79 | timeout: const Duration(seconds: 30), 80 | ), 81 | ); 82 | ``` 83 | 2. Minimize the amount of data passed to and from functions to reduce latency. 84 | 3. Use batch operations when possible to reduce the number of function calls. 85 | 4. Consider implementing client-side caching for frequently used function results. 86 | 5. Monitor function performance using Firebase Console to identify bottlenecks. 87 | 6. Be mindful of cold starts for infrequently used functions. 88 | 7. Implement proper loading states in your UI while waiting for function responses. 89 | 90 | ### Testing and Development 91 | 92 | 1. Use the Firebase Emulator Suite for local development and testing. 93 | ```dart 94 | FirebaseFunctions.instance.useFunctionsEmulator('localhost', 5001); 95 | ``` 96 | 2. Test functions with various input data to ensure robust error handling. 97 | 3. Implement unit tests for client-side function calling logic. 98 | 4. Use integration tests to verify end-to-end function behavior. 99 | 5. Test functions with both valid and invalid inputs to ensure proper validation. 100 | 6. Verify that functions handle authentication correctly. 101 | 7. Test functions with different user roles and permissions to ensure proper access control. 102 | -------------------------------------------------------------------------------- /rules/firebase/firebase_analytics.md: -------------------------------------------------------------------------------- 1 | # Firebase Analytics Rules 2 | 3 | ### Setup and Configuration 4 | 5 | 1. Install Firebase Analytics using `flutter pub add firebase_analytics` in your Flutter project. 6 | 2. Rebuild your Flutter application using `flutter run` after installation. 7 | 3. Import the package in your Dart code using `import 'package:firebase_analytics/firebase_analytics.dart';`. 8 | 4. Create a new Firebase Analytics instance by accessing the `instance` property on `FirebaseAnalytics`. 9 | ```dart 10 | FirebaseAnalytics analytics = FirebaseAnalytics.instance; 11 | ``` 12 | 5. Initialize Firebase before using any Firebase Analytics features. 13 | 14 | ### Event Logging 15 | 16 | 1. Use predefined event types when possible to get maximum detail in reports and benefit from the latest Google Analytics features. 17 | 2. Log events using the library's dedicated methods for recommended event types. 18 | ```dart 19 | await FirebaseAnalytics.instance.logSelectContent( 20 | contentType: "image", 21 | itemId: itemId, 22 | ); 23 | ``` 24 | 3. Alternatively, use the general `logEvent()` method for both predefined and custom events. 25 | ```dart 26 | await FirebaseAnalytics.instance.logEvent( 27 | name: "select_content", 28 | parameters: { 29 | "content_type": "image", 30 | "item_id": itemId, 31 | }, 32 | ); 33 | ``` 34 | 4. For custom events, use the `logEvent()` method with a descriptive name and relevant parameters. 35 | ```dart 36 | await FirebaseAnalytics.instance.logEvent( 37 | name: "share_image", 38 | parameters: { 39 | "image_name": name, 40 | "full_text": text, 41 | }, 42 | ); 43 | ``` 44 | 5. Event names are case-sensitive; logging two events whose names differ only in case will result in two distinct events. 45 | 6. You can log up to 500 different Analytics Event types in your app with no limit on the total volume of events. 46 | 47 | ### Parameters and Properties 48 | 49 | 1. Parameter names can be up to 40 characters long and must start with an alphabetic character. 50 | 2. Parameter names must contain only alphanumeric characters and underscores. 51 | 3. String parameter values can be up to 100 characters long. 52 | 4. The "firebase_", "google_" and "ga_" prefixes are reserved and shouldn't be used for parameter names. 53 | 5. Use custom parameters for non-numerical event parameter data (dimensions) or numerical data (metrics). 54 | 6. Register custom dimensions or metrics in the Analytics console to ensure they appear in reports. 55 | 7. Set default event parameters using `setDefaultEventParameters()` to associate parameters with all future events. 56 | ```dart 57 | // Not supported on web 58 | await FirebaseAnalytics.instance 59 | .setDefaultEventParameters({ 60 | version: '1.2.3' 61 | }); 62 | ``` 63 | 8. To clear a default parameter, call `setDefaultEventParameters()` with the parameter set to `null`. 64 | 65 | ### User Properties 66 | 67 | 1. Set user properties to describe segments of your user base using the `setUserProperty()` method. 68 | ```dart 69 | await FirebaseAnalytics.instance 70 | .setUserProperty( 71 | name: 'favorite_food', 72 | value: favoriteFood, 73 | ); 74 | ``` 75 | 2. Create custom definitions for user properties in the Analytics console before using them in your app. 76 | 3. Use user properties for creating custom definitions, applying comparisons in reports, or as audience evaluation criteria. 77 | 4. Keep user property names and values concise and relevant to your analytics needs. 78 | 79 | ### Best Practices 80 | 81 | 1. Analytics automatically logs some events and user properties; you don't need to add code to enable them. 82 | 2. Request necessary permissions before collecting user data, especially on platforms with strict privacy controls. 83 | 3. Avoid logging sensitive or personally identifiable information in events or user properties. 84 | 4. Use consistent naming conventions for custom events and parameters to make analysis easier. 85 | 5. Group related events to track user flows and conversion funnels effectively. 86 | 6. Consider the analytics data you'll need for decision-making before implementing tracking to ensure you collect the right information. 87 | 7. Test your analytics implementation to verify events are being logged correctly before deploying to production. 88 | -------------------------------------------------------------------------------- /rules/firebase/firebase_app_check.md: -------------------------------------------------------------------------------- 1 | # Firebase App Check Rules 2 | 3 | ### Setup and Configuration 4 | 5 | 1. Install the Firebase App Check plugin using `flutter pub add firebase_app_check`. 6 | 2. Import the plugin in your Dart code using `import 'package:firebase_app_check/firebase_app_check.dart';`. 7 | 3. Register your apps in the Firebase console under Project Settings > App Check before using the service. 8 | 4. Consider setting a custom time-to-live (TTL) for App Check tokens based on your security and performance needs. 9 | 5. Be aware that shorter TTLs provide stronger security but may impact performance and consume quota faster. 10 | 6. Initialize App Check after Firebase initialization but before using any Firebase services. 11 | ```dart 12 | await Firebase.initializeApp(); 13 | await FirebaseAppCheck.instance.activate( 14 | webProvider: ReCaptchaV3Provider('recaptcha-v3-site-key'), 15 | androidProvider: AndroidProvider.playIntegrity, 16 | appleProvider: AppleProvider.deviceCheck, 17 | ); 18 | ``` 19 | 7. For web applications, obtain a reCAPTCHA v3 site key from the Firebase console and use it with the `ReCaptchaV3Provider`. 20 | 21 | ### Provider Selection 22 | 23 | 1. For Android, choose the appropriate provider based on your requirements: 24 | - `AndroidProvider.playIntegrity` (default): Uses Play Integrity API for app verification 25 | - `AndroidProvider.safetyNet`: Uses the legacy SafetyNet API (not recommended for new apps) 26 | - `AndroidProvider.debug`: For development and testing environments only 27 | 2. For Apple platforms (iOS/macOS), choose the appropriate provider: 28 | - `AppleProvider.deviceCheck` (default): Works on iOS 11+ and macOS 10.15+ 29 | - `AppleProvider.appAttest`: Enhanced security on iOS 14+ and macOS 14+ 30 | - `AppleProvider.appAttestWithDeviceCheckFallback`: Uses App Attest with fallback to Device Check 31 | - `AppleProvider.debug`: For development and testing environments only 32 | 3. For web platforms, use one of the following providers: 33 | - `ReCaptchaV3Provider`: Standard reCAPTCHA v3 verification 34 | - `ReCaptchaEnterpriseProvider`: Enhanced version with additional features 35 | 36 | ### Development and Testing 37 | 38 | 1. Use the debug provider during development to run your app in emulators or CI environments. 39 | ```dart 40 | await FirebaseAppCheck.instance.activate( 41 | androidProvider: AndroidProvider.debug, 42 | appleProvider: AppleProvider.debug, 43 | ); 44 | ``` 45 | 2. For iOS debug builds, enable debug logging by adding `-FIRDebugEnabled` to the Arguments Passed on Launch in Xcode. 46 | 3. For web debug builds, enable debug mode by setting `self.FIREBASE_APPCHECK_DEBUG_TOKEN = true;` in your `web/index.html` file. 47 | 4. Register the debug tokens displayed in the console in the Firebase console's App Check section. 48 | 5. Never use debug providers or share debug tokens in production builds. 49 | 6. Keep debug tokens private and don't commit them to public repositories. 50 | 7. Revoke compromised debug tokens immediately from the Firebase console. 51 | 52 | ### Enforcement and Monitoring 53 | 54 | 1. Monitor App Check metrics before enabling enforcement to ensure it won't disrupt legitimate users. 55 | 2. Enable enforcement gradually, starting with non-critical Firebase services. 56 | 3. Monitor request metrics for Realtime Database, Cloud Firestore, Cloud Storage, and Authentication to understand usage patterns. 57 | 4. Be aware that once enforcement is enabled, only registered apps with valid App Check tokens can access your Firebase resources. 58 | 5. Consider enabling enforcement sooner if you observe suspicious usage of your app resources. 59 | 6. Use App Check in combination with Firebase Security Rules for comprehensive security. 60 | 7. Implement proper error handling for App Check verification failures in your app. 61 | 62 | ### Security Best Practices 63 | 64 | 1. Never disable App Check in production builds once you've enabled it. 65 | 2. Implement a fallback mechanism for handling App Check verification failures. 66 | 3. Regularly review App Check metrics to identify potential abuse patterns. 67 | 4. Use App Check in conjunction with other security measures like authentication and security rules. 68 | 5. Be aware that App Check tokens are automatically refreshed at approximately half the TTL duration. 69 | 6. For high-security applications, use the shortest practical TTL for App Check tokens. 70 | 7. Implement server-side verification for critical operations using Firebase Admin SDK. 71 | -------------------------------------------------------------------------------- /rules/firebase/firebase_auth.md: -------------------------------------------------------------------------------- 1 | # Firebase Authentication Rules 2 | 3 | ### Setup and Configuration 4 | 5 | 1. Install the Firebase Authentication plugin using `flutter pub add firebase_auth`. 6 | 2. Import the plugin in your Dart code using `import 'package:firebase_auth/firebase_auth.dart';`. 7 | 3. Enable desired authentication providers in the Firebase console before using them in your app. 8 | 4. For testing, use the Firebase Local Emulator Suite by calling `useAuthEmulator()` to specify the emulator address and port. 9 | ```dart 10 | Future main() async { 11 | WidgetsFlutterBinding.ensureInitialized(); 12 | await Firebase.initializeApp(); 13 | 14 | // Ideal time to initialize 15 | await FirebaseAuth.instance.useAuthEmulator('localhost', 9099); 16 | //... 17 | } 18 | ``` 19 | 5. Initialize Firebase before using any Firebase Authentication features. 20 | 21 | ### Authentication State Management 22 | 23 | 1. Use `authStateChanges()` to listen for basic sign-in state changes (signed in or signed out). 24 | ```dart 25 | FirebaseAuth.instance 26 | .authStateChanges() 27 | .listen((User? user) { 28 | if (user == null) { 29 | print('User is currently signed out!'); 30 | } else { 31 | print('User is signed in!'); 32 | } 33 | }); 34 | ``` 35 | 2. Use `idTokenChanges()` to listen for changes in the user's ID token, including custom claims updates. 36 | ```dart 37 | FirebaseAuth.instance 38 | .idTokenChanges() 39 | .listen((User? user) { 40 | if (user == null) { 41 | print('User is currently signed out!'); 42 | } else { 43 | print('User is signed in!'); 44 | } 45 | }); 46 | ``` 47 | 3. Use `userChanges()` to listen for changes to the user's data, such as profile updates. 48 | 4. Always handle the initial authentication state when your app starts by listening to these streams immediately. 49 | 5. When using custom claims, be aware they will only be available after sign-in, re-authentication, token expiration, or manual token refresh. 50 | 51 | ### Email and Password Authentication 52 | 53 | 1. Create a new user account with email and password using `createUserWithEmailAndPassword()`. 54 | ```dart 55 | try { 56 | final credential = await FirebaseAuth.instance.createUserWithEmailAndPassword( 57 | email: emailAddress, 58 | password: password, 59 | ); 60 | } on FirebaseAuthException catch (e) { 61 | if (e.code == 'weak-password') { 62 | print('The password provided is too weak.'); 63 | } else if (e.code == 'email-already-in-use') { 64 | print('The account already exists for that email.'); 65 | } 66 | } catch (e) { 67 | print(e); 68 | } 69 | ``` 70 | 2. Sign in existing users with email and password using `signInWithEmailAndPassword()`. 71 | ```dart 72 | try { 73 | final credential = await FirebaseAuth.instance.signInWithEmailAndPassword( 74 | email: emailAddress, 75 | password: password 76 | ); 77 | } on FirebaseAuthException catch (e) { 78 | if (e.code == 'user-not-found') { 79 | print('No user found for that email.'); 80 | } else if (e.code == 'wrong-password') { 81 | print('Wrong password provided for that user.'); 82 | } 83 | } 84 | ``` 85 | 3. Verify the user's email address after account creation to enhance security. 86 | 4. Be aware that Firebase limits the number of new email/password sign-ups from the same IP address in a short period to protect against abuse. 87 | 5. On iOS and macOS, be aware that authentication state can persist between app re-installs as the Firebase SDK persists authentication state to the system keychain. 88 | 89 | ### Social Authentication 90 | 91 | 1. Enable the desired social authentication providers in the Firebase console before implementing them in your app. 92 | 2. For Google Sign-In on native platforms, use the `google_sign_in` plugin and create a credential with the authentication details. 93 | ```dart 94 | Future signInWithGoogle() async { 95 | // Trigger the authentication flow 96 | final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn(); 97 | 98 | // Obtain the auth details from the request 99 | final GoogleSignInAuthentication? googleAuth = await googleUser?.authentication; 100 | 101 | // Create a new credential 102 | final credential = GoogleAuthProvider.credential( 103 | accessToken: googleAuth?.accessToken, 104 | idToken: googleAuth?.idToken, 105 | ); 106 | 107 | // Once signed in, return the UserCredential 108 | return await FirebaseAuth.instance.signInWithCredential(credential); 109 | } 110 | ``` 111 | 3. For web platforms, use the built-in Firebase SDK methods for handling authentication flow. 112 | ```dart 113 | Future signInWithGoogle() async { 114 | // Create a new provider 115 | GoogleAuthProvider googleProvider = GoogleAuthProvider(); 116 | 117 | googleProvider.addScope('https://www.googleapis.com/auth/contacts.readonly'); 118 | googleProvider.setCustomParameters({ 119 | 'login_hint': 'user@example.com' 120 | }); 121 | 122 | // Once signed in, return the UserCredential 123 | return await FirebaseAuth.instance.signInWithPopup(googleProvider); 124 | } 125 | ``` 126 | 4. Configure platform-specific settings for each social provider (e.g., SHA1 key for Google Sign-In on Android). 127 | 5. Be aware that if a user signs in with a social provider after having manually registered an account with the same email, their authentication provider will automatically change due to Firebase's concept of trusted providers. 128 | 129 | ### Error Handling 130 | 131 | 1. Use try-catch blocks with `FirebaseAuthException` to handle authentication errors. 132 | 2. Check the `code` property of `FirebaseAuthException` to identify specific error types. 133 | 3. Handle `account-exists-with-different-credential` errors by fetching sign-in methods for the email and guiding users through the appropriate sign-in flow. 134 | 4. Be prepared to handle `too-many-requests` errors by implementing appropriate retry logic or user feedback. 135 | 5. Handle `operation-not-allowed` errors by ensuring the authentication provider is enabled in the Firebase console. 136 | 6. Implement proper error messages for common authentication failures to improve user experience. 137 | 138 | ### User Management 139 | 140 | 1. Store only essential user information in the authentication profile and use a database for additional user data. 141 | 2. When linking authentication providers, always verify the user's identity before linking new credentials. 142 | 3. When handling account linking, use `fetchSignInMethodsForEmail()` to determine the appropriate sign-in method. 143 | 4. Use `linkWithCredential()` to connect multiple authentication providers to a single user account. 144 | 5. Implement proper error handling when linking accounts to handle cases where the account may already exist. 145 | 6. Access the user's basic profile information from the `User` object after authentication. 146 | 7. Use the `updateProfile()` method to update the user's display name and photo URL. 147 | ```dart 148 | await FirebaseAuth.instance.currentUser?.updateProfile( 149 | displayName: "Jane Q. User", 150 | photoURL: "https://example.com/jane-q-user/profile.jpg", 151 | ); 152 | ``` 153 | 154 | ### Security Best Practices 155 | 156 | 1. Never store sensitive authentication credentials in client-side code. 157 | 2. Implement proper session management by monitoring authentication state changes. 158 | 3. Use multi-factor authentication for sensitive applications to enhance security. 159 | 4. Validate user input before submitting authentication requests to prevent injection attacks. 160 | 5. Implement proper logout functionality by calling `FirebaseAuth.instance.signOut()` when users exit the app. 161 | 6. For sensitive operations, consider re-authenticating users with `reauthenticateWithCredential()`. 162 | 7. When using email/password authentication, enforce strong password policies. 163 | 8. In Firebase Realtime Database and Cloud Storage Security Rules, use the `auth` variable to get the signed-in user's unique ID for access control. 164 | -------------------------------------------------------------------------------- /rules/firebase/firebase_crashlytics.md: -------------------------------------------------------------------------------- 1 | # Firebase Crashlytics Rules 2 | 3 | ### Setup and Configuration 4 | 5 | 1. Install the Firebase Crashlytics plugin using `flutter pub add firebase_crashlytics`. 6 | 2. Install Firebase Analytics using `flutter pub add firebase_analytics` to enable breadcrumb logs for better crash context. 7 | 3. Run `flutterfire configure` to ensure your Flutter app's Firebase configuration is up-to-date and add required Crashlytics Gradle plugin for Android. 8 | 4. Import the plugin in your Dart code using `import 'package:firebase_crashlytics/firebase_crashlytics.dart';`. 9 | 5. For obfuscated code (using `--split-debug-info` and/or `--obfuscate` flags), ensure proper symbol file uploading for readable stack traces. 10 | 6. For iOS platforms with obfuscated code, use Flutter 3.12.0+ and Crashlytics Flutter plugin 3.3.4+ for automatic symbol file generation and uploading. 11 | 7. For Android with obfuscated code, use Firebase CLI (v.11.9.0+) to upload Flutter debug symbols before reporting crashes. 12 | ```bash 13 | firebase crashlytics:symbols:upload --app=FIREBASE_APP_ID PATH/TO/symbols 14 | ``` 15 | 16 | ### Error Handling 17 | 18 | 1. Configure Flutter framework error handling by overriding `FlutterError.onError` with `FirebaseCrashlytics.instance.recordFlutterFatalError`. 19 | ```dart 20 | void main() async { 21 | WidgetsFlutterBinding.ensureInitialized(); 22 | await Firebase.initializeApp(); 23 | 24 | // Pass all uncaught "fatal" errors from the framework to Crashlytics 25 | FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError; 26 | 27 | runApp(MyApp()); 28 | } 29 | ``` 30 | 2. For non-fatal Flutter errors, use `FirebaseCrashlytics.instance.recordFlutterError` instead. 31 | 3. Catch asynchronous errors that aren't handled by the Flutter framework using `PlatformDispatcher.instance.onError`. 32 | ```dart 33 | PlatformDispatcher.instance.onError = (error, stack) { 34 | FirebaseCrashlytics.instance.recordError(error, stack, fatal: true); 35 | return true; 36 | }; 37 | ``` 38 | 4. For errors outside of the Flutter context, install an error listener on the current `Isolate`. 39 | ```dart 40 | Isolate.current.addErrorListener(RawReceivePort((pair) async { 41 | final List errorAndStacktrace = pair; 42 | await FirebaseCrashlytics.instance.recordError( 43 | errorAndStacktrace.first, 44 | errorAndStacktrace.last, 45 | fatal: true, 46 | ); 47 | }).sendPort); 48 | ``` 49 | 5. Report caught exceptions using `recordError` to track non-fatal issues. 50 | ```dart 51 | await FirebaseCrashlytics.instance.recordError( 52 | error, 53 | stackTrace, 54 | reason: 'a non-fatal error' 55 | ); 56 | ``` 57 | 6. Be aware that Crashlytics only stores the most recent eight recorded non-fatal exceptions, with older ones being lost. 58 | 7. Include additional diagnostic information about errors using the `information` parameter. 59 | ```dart 60 | await FirebaseCrashlytics.instance.recordError( 61 | error, 62 | stackTrace, 63 | reason: 'a non-fatal error', 64 | information: ['further diagnostic information about the error', 'version 2.0'], 65 | ); 66 | ``` 67 | 68 | ### Crash Report Customization 69 | 70 | 1. Add custom keys to provide specific state information about your app leading up to a crash. 71 | ```dart 72 | FirebaseCrashlytics.instance.setCustomKey('str_key', 'hello'); 73 | FirebaseCrashlytics.instance.setCustomKey('bool_key', true); 74 | FirebaseCrashlytics.instance.setCustomKey('int_key', 1); 75 | ``` 76 | 2. Limit custom keys to a maximum of 64 key/value pairs, with each pair up to 1 kB in size. 77 | 3. Add custom log messages to provide more context for events leading up to a crash. 78 | ```dart 79 | FirebaseCrashlytics.instance.log("User tapped on payment button"); 80 | ``` 81 | 4. Be aware that Crashlytics limits logs to 64kB and deletes older log entries when a session's logs exceed that limit. 82 | 5. Set user identifiers to help diagnose issues for specific users. 83 | ```dart 84 | FirebaseCrashlytics.instance.setUserIdentifier("user-123"); 85 | ``` 86 | 6. Clear user identifiers by setting them to a blank string when needed. 87 | 7. Avoid including unique values (like user IDs or timestamps) directly in exception messages; use custom keys instead. 88 | 89 | ### Performance and Optimization 90 | 91 | 1. Be aware that Crashlytics processes exceptions on a dedicated background thread to minimize performance impact. 92 | 2. For fatal reports, Crashlytics sends them in real-time without requiring app restart. 93 | 3. For non-fatal reports, Crashlytics writes them to disk to be sent with the next fatal report or when the app restarts. 94 | 4. Crashlytics rate-limits the number of reports sent from a device to reduce network traffic if necessary. 95 | 5. Implement proper error handling in critical code paths to avoid unnecessary crashes. 96 | 6. Use breadcrumb logs to understand user actions leading up to a crash, which requires Firebase Analytics integration. 97 | 7. Consider disabling Crashlytics in debug builds if you want to avoid reporting development crashes. 98 | ```dart 99 | // Only enable Crashlytics in non-debug builds 100 | if (kReleaseMode) { 101 | await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(true); 102 | } else { 103 | await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(false); 104 | } 105 | ``` 106 | 107 | ### Testing and Debugging 108 | 109 | 1. Force a test crash to verify your Crashlytics setup is working correctly. 110 | ```dart 111 | FirebaseCrashlytics.instance.crash(); 112 | ``` 113 | 2. Test both fatal and non-fatal error reporting to ensure proper configuration. 114 | 3. Verify that stack traces are properly symbolicated, especially when using code obfuscation. 115 | 4. Check that custom keys, logs, and user identifiers are properly associated with crash reports. 116 | 5. Test error handling in different app states and scenarios to ensure comprehensive crash reporting. 117 | 6. Monitor the Crashlytics dashboard regularly to identify and address emerging issues. 118 | 7. Use the Firebase console to set up alerts for new issues or regressions. 119 | -------------------------------------------------------------------------------- /rules/firebase/firebase_database.md: -------------------------------------------------------------------------------- 1 | # Firebase Realtime Database Rules 2 | 3 | ### Database Selection 4 | 5 | 1. Choose Realtime Database for applications with simple data models requiring simple lookups and extremely low-latency synchronization (typical response times under 10ms). 6 | 2. Use Realtime Database for applications that need deep queries by default, which return the entire subtree. 7 | 3. Consider Realtime Database when you need to access data at any granularity, down to individual leaf-node values in the JSON tree. 8 | 4. Select Realtime Database for applications that require extremely low latency for frequent state-syncing. 9 | 5. Choose Realtime Database when your application needs to track client connection status with built-in presence functionality. 10 | 6. Consider Cloud Firestore instead for applications with rich data models requiring queryability, scalability, and high availability. 11 | 12 | ### Setup and Configuration 13 | 14 | 1. Install Firebase Realtime Database using `flutter pub add firebase_database` in your Flutter project. 15 | 2. Import the package in your Dart code using `import 'package:firebase_database/firebase_database.dart';`. 16 | 3. Initialize Realtime Database with `final DatabaseReference ref = FirebaseDatabase.instance.ref();` after Firebase has been initialized. 17 | 4. Select the database location closest to your users to minimize latency. 18 | 5. Consider enabling persistence for offline capabilities. 19 | ```dart 20 | FirebaseDatabase.instance.setPersistenceEnabled(true); 21 | ``` 22 | 6. Set a cache size limit if needed for performance optimization. 23 | ```dart 24 | FirebaseDatabase.instance.setPersistenceCacheSizeBytes(10000000); // 10MB 25 | ``` 26 | 27 | ### Data Structure 28 | 29 | 1. Structure your data as a JSON tree with a flattened hierarchy to avoid deep nesting. 30 | 2. Avoid nesting data more than 32 levels deep, as this is the maximum depth allowed. 31 | 3. Design your data structure to support your most common queries, as the structure directly impacts how you can retrieve data. 32 | 4. Use push IDs when you need to create unique identifiers for list-type data. 33 | ```dart 34 | // Generate a unique key 35 | final newPostKey = FirebaseDatabase.instance.ref().child('posts').push().key; 36 | ``` 37 | 5. Denormalize data when necessary to avoid complex joins, as Realtime Database doesn't support joins natively. 38 | 6. Keep your data structure shallow to improve read and write performance. 39 | 7. When creating your own keys, they must be UTF-8 encoded, can be a maximum of 768 bytes, and cannot contain `.`, `$`, `#`, `[`, `]`, `/`, or ASCII control characters 0-31 or 127. 40 | 8. Do not use ASCII control characters in the values themselves. 41 | 42 | ### Indexing and Querying 43 | 44 | 1. Queries can sort or filter on a property, but not both in the same query. 45 | 2. Index your data structure for frequently accessed paths to improve read performance. 46 | 3. Use the `.indexOn` rule in your security rules to specify which child keys should be indexed. 47 | ```json 48 | { 49 | "rules": { 50 | "dinosaurs": { 51 | ".indexOn": ["height", "length"] 52 | } 53 | } 54 | } 55 | ``` 56 | 4. Be aware that queries are deep by default and always return the entire subtree. 57 | 5. Use the `orderByChild()`, `orderByKey()`, or `orderByValue()` methods to specify how data should be sorted. 58 | ```dart 59 | // Order results by child value 60 | final ref = FirebaseDatabase.instance.ref("dinosaurs"); 61 | final query = ref.orderByChild("height"); 62 | ``` 63 | 6. Limit query results using `limitToFirst()` or `limitToLast()` to improve performance and reduce data transfer. 64 | ```dart 65 | // Get first 10 dinosaurs ordered by height 66 | final query = ref.orderByChild("height").limitToFirst(10); 67 | ``` 68 | 69 | ### Read and Write Operations 70 | 71 | 1. Use asynchronous calls instead of synchronous calls to minimize latency impact. 72 | ```dart 73 | // Read data once 74 | final snapshot = await FirebaseDatabase.instance.ref('users/123').get(); 75 | if (snapshot.exists) { 76 | print(snapshot.value); 77 | } 78 | ``` 79 | 2. Use listeners for real-time updates rather than repeatedly polling for data. 80 | ```dart 81 | // Set up a listener 82 | FirebaseDatabase.instance.ref('users/123').onValue.listen((event) { 83 | final data = event.snapshot.value; 84 | print(data); 85 | }); 86 | ``` 87 | 3. Use `set()` to write or replace data at a specific reference. 88 | ```dart 89 | await ref.set({ 90 | "name": "John", 91 | "age": 18, 92 | "address": { 93 | "line1": "100 Mountain View" 94 | } 95 | }); 96 | ``` 97 | 4. Use `update()` to update specific fields without overwriting the entire object. 98 | ```dart 99 | await ref.update({ 100 | "age": 19, 101 | }); 102 | ``` 103 | 5. Use transactions for operations that require atomic updates. 104 | ```dart 105 | FirebaseDatabase.instance.ref('posts/123/likes').runTransaction((currentValue) { 106 | return (currentValue as int? ?? 0) + 1; 107 | }); 108 | ``` 109 | 6. Use multi-path updates to update multiple locations atomically. 110 | ```dart 111 | final updates = { 112 | 'posts/$postId': postData, 113 | 'user-posts/$uid/$postId': postData, 114 | }; 115 | FirebaseDatabase.instance.ref().update(updates); 116 | ``` 117 | 7. Keep individual write operations under 256KB to comply with size limits. 118 | 8. Be aware that a `DatabaseEvent` is fired every time data is changed at the specified reference, including changes to children. 119 | 120 | ### Designing for Scale 121 | 122 | 1. Be aware that Realtime Database scales to around 200,000 concurrent connections and 1,000 writes/second in a single database. 123 | 2. For applications requiring higher scale, shard your data across multiple database instances. 124 | 3. Avoid storing large blobs of data; use Firebase Storage instead for files. 125 | 4. Use server timestamps for consistent time tracking across clients. 126 | ```dart 127 | final serverTimestamp = ServerValue.timestamp; 128 | FirebaseDatabase.instance.ref('posts/123/timestamp').set(serverTimestamp); 129 | ``` 130 | 5. Implement fan-out patterns for data that needs to be accessed from multiple paths. 131 | 6. Monitor your database usage and performance using Firebase console. 132 | 7. Flatten data structures to avoid deep nesting, which can lead to performance issues when retrieving data. 133 | 134 | ### Offline Capabilities 135 | 136 | 1. Enable disk persistence to allow your app to work offline. 137 | ```dart 138 | FirebaseDatabase.instance.setPersistenceEnabled(true); 139 | ``` 140 | 2. Use `keepSynced(true)` for critical paths that should be kept in sync even when offline. 141 | ```dart 142 | FirebaseDatabase.instance.ref('important-data').keepSynced(true); 143 | ``` 144 | 3. Handle connection state changes to provide feedback to users. 145 | ```dart 146 | FirebaseDatabase.instance.ref('.info/connected').onValue.listen((event) { 147 | final connected = event.snapshot.value as bool? ?? false; 148 | print('Connected: $connected'); 149 | }); 150 | ``` 151 | 4. Implement proper error handling for operations that may fail due to connectivity issues. 152 | 5. Be aware of the cache size limitations when enabling persistence. 153 | 6. Generally, use value events to read data and get notified of updates from the backend, which reduces usage and billing and is optimized for online/offline transitions. 154 | 7. Use `get()` only when you need the data once, as it will probe the local storage cache if the server value is unavailable. 155 | 156 | ### Security 157 | 158 | 1. Always use Firebase Realtime Database Security Rules to protect your data. 159 | 2. Structure security rules to match your data structure for efficient validation. 160 | 3. Use the `auth` variable to authenticate users in security rules. 161 | 4. Validate data structure and content using the `validate` rule. 162 | 5. Be aware that read and write rules cascade in Realtime Database. 163 | 6. Use the `.read`, `.write`, `.validate`, and `.indexOn` rules to control access and validate data. 164 | ```json 165 | { 166 | "rules": { 167 | "users": { 168 | "$uid": { 169 | ".read": "$uid === auth.uid", 170 | ".write": "$uid === auth.uid" 171 | } 172 | } 173 | } 174 | } 175 | ``` 176 | 7. Test your security rules thoroughly using the Firebase console's rules simulator. 177 | 8. For testing purposes, you can use the Firebase Emulator Suite to prototype and test Realtime Database functionality locally. 178 | -------------------------------------------------------------------------------- /rules/firebase/firebase_in_app_messaging.md: -------------------------------------------------------------------------------- 1 | # Firebase In-App Messaging Rules 2 | 3 | ### Setup and Configuration 4 | 5 | 1. Install the Firebase In-App Messaging plugin using `flutter pub add firebase_in_app_messaging`. 6 | 2. Import the plugin in your Dart code using `import 'package:firebase_in_app_messaging/firebase_in_app_messaging.dart';`. 7 | 3. Ensure Firebase is properly initialized before using any Firebase In-App Messaging features. 8 | 4. Be aware that Firebase In-App Messaging only retrieves messages from the server once per day by default to conserve power. 9 | 5. For testing on Android, look for the Installation ID in console logs with the format `I/FIAM.Headless: Starting InAppMessaging runtime with Installation ID YOUR_INSTALLATION_ID`. 10 | 6. For testing on iOS, enable debug mode by adding the `-FIRDebugEnabled` runtime argument in Xcode's scheme settings. 11 | 12 | ### Message Triggering and Display 13 | 14 | 1. Use Google Analytics for Firebase events to trigger in-app messages without additional integration. 15 | 2. Trigger in-app messages programmatically using the `triggerEvent` method when needed. 16 | ```dart 17 | FirebaseInAppMessaging.instance.triggerEvent("eventName"); 18 | ``` 19 | 3. Temporarily suppress message displays during critical app flows (like payment processing) using `setMessagesSuppressed`. 20 | ```dart 21 | FirebaseInAppMessaging.instance.setMessagesSuppressed(true); 22 | ``` 23 | 4. Re-enable message display by calling `setMessagesSuppressed(false)` when appropriate. 24 | 5. Be aware that message suppression is automatically turned off on app restart. 25 | 6. Understand that suppressed messages are ignored and their trigger conditions must be met again after suppression is turned off. 26 | 7. For platform-specific message interaction handling, use the native iOS and Android APIs as Flutter doesn't provide direct access to these callbacks. 27 | 28 | ### User Privacy and Data Collection 29 | 30 | 1. By default, Firebase In-App Messaging automatically delivers messages to all targeted app users. 31 | 2. To implement opt-in data collection for privacy-conscious users, disable automatic initialization first. 32 | 3. For iOS, disable automatic data collection by adding `FirebaseInAppMessagingAutomaticDataCollectionEnabled` with value `NO` to your `Info.plist` file. 33 | 4. For Android, disable automatic data collection by adding the following to your `AndroidManifest.xml`: 34 | ```xml 35 | 38 | ``` 39 | 5. Manually enable data collection for users who opt in: 40 | ```dart 41 | FirebaseInAppMessaging.instance.setAutomaticDataCollectionEnabled(true); 42 | ``` 43 | 6. Be aware that manually set data collection preferences persist through app restarts, overriding the values in configuration files. 44 | 45 | ### Testing and Debugging 46 | 47 | 1. Use the Firebase console's test device feature to send test messages on demand to specific devices. 48 | 2. Always test in-app messages on actual devices to ensure proper rendering and behavior. 49 | 3. When testing, use the Firebase Installation ID (FID) to target specific test devices. 50 | 4. Create test campaigns in the Firebase console by selecting "Test on your Device" and entering your app's Firebase Installation ID. 51 | 5. For iOS testing, check the Xcode console logs for the line containing `[Firebase/InAppMessaging][I-IAM180017]` to find your Installation ID. 52 | 6. For Android testing, filter logcat output for "FIAM.Headless" to locate your Installation ID. 53 | 54 | ### Campaign Management 55 | 56 | 1. Create in-app messaging campaigns through the Firebase console under the Messaging section. 57 | 2. Design campaigns with appropriate message types (modal, banner, card, or image-only) based on the content and user experience requirements. 58 | 3. Use campaign custom metadata (key-value pairs) to include additional information that can be accessed when users interact with messages. 59 | 4. Target messages based on user segments, app version, language, and other Analytics-based conditions. 60 | 5. Schedule campaigns based on specific events, user properties, or conversion events. 61 | 6. Monitor campaign performance through the Firebase console analytics to measure effectiveness and user engagement. 62 | 7. Use A/B testing features to optimize message content and delivery for better user engagement. 63 | -------------------------------------------------------------------------------- /rules/firebase/firebase_messaging.md: -------------------------------------------------------------------------------- 1 | # Firebase Cloud Messaging Rules 2 | 3 | ### Setup and Configuration 4 | 5 | 1. Enable push notifications and background modes in your Xcode project for iOS targets. 6 | 2. Upload your APNs authentication key to Firebase before using FCM on iOS. 7 | 3. Do not disable method swizzling on Apple devices, as it's required for FCM token handling. 8 | 4. Request user permission before FCM payloads can be received on iOS, macOS, web, and Android 13 or newer. 9 | 5. For iOS, ensure the bundle ID for your APNs certificate matches the bundle ID of your app. 10 | 6. Install the FCM plugin using `flutter pub add firebase_messaging`. 11 | 7. Ensure your Android devices are running Android 4.4 or higher with Google Play services installed. 12 | 8. Check for Google Play services compatibility in both `onCreate()` and `onResume()` methods for Android. 13 | 14 | ### Message Handling 15 | 16 | 1. Use `FirebaseMessaging.onMessage.listen` to handle messages while your application is in the foreground. 17 | ```dart 18 | FirebaseMessaging.onMessage.listen((RemoteMessage message) { 19 | print('Got a message whilst in the foreground!'); 20 | print('Message data: ${message.data}'); 21 | 22 | if (message.notification != null) { 23 | print('Message also contained a notification: ${message.notification}'); 24 | } 25 | }); 26 | ``` 27 | 28 | 2. Use `FirebaseMessaging.onBackgroundMessage` to register a handler for background messages. 29 | ```dart 30 | @pragma('vm:entry-point') 31 | Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { 32 | // If you're going to use other Firebase services in the background, such as Firestore, 33 | // make sure you call `initializeApp` before using other Firebase services. 34 | await Firebase.initializeApp(); 35 | 36 | print("Handling a background message: ${message.messageId}"); 37 | } 38 | 39 | void main() { 40 | FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); 41 | runApp(MyApp()); 42 | } 43 | ``` 44 | 45 | 3. Background message handlers must not be anonymous functions. 46 | 4. Background message handlers must be top-level functions, not class methods which require initialization. 47 | 5. When using Flutter version 3.3.0 or higher, annotate background message handlers with `@pragma('vm:entry-point')` right above the function declaration to prevent removal during tree shaking for release mode. 48 | 6. Initialize Firebase before using other Firebase services in background message handlers. 49 | 7. Background message handlers cannot update application state or execute UI-impacting logic as they run in a separate isolate. 50 | 51 | ### Permissions 52 | 53 | 1. Use `requestPermission()` method to request user permission for receiving notifications. 54 | ```dart 55 | FirebaseMessaging messaging = FirebaseMessaging.instance; 56 | 57 | NotificationSettings settings = await messaging.requestPermission( 58 | alert: true, 59 | announcement: false, 60 | badge: true, 61 | carPlay: false, 62 | criticalAlert: false, 63 | provisional: false, 64 | sound: true, 65 | ); 66 | 67 | print('User granted permission: ${settings.authorizationStatus}'); 68 | ``` 69 | 70 | 2. Check the `authorizationStatus` property of the returned `NotificationSettings` to determine the user's decision. 71 | 3. For Android versions prior to 13, be aware that `authorizationStatus` returns `authorized` if the user has not disabled notifications in the OS settings. 72 | 4. For Android 13 and above, track permission requests in your app as there's no way to determine if the user has chosen to grant/deny permission. 73 | 5. Consider using provisional permissions on iOS by setting `provisional: true` to allow users to choose notification types after receiving their first notification. 74 | ```dart 75 | final notificationSettings = await FirebaseMessaging.instance.requestPermission(provisional: true); 76 | ``` 77 | 78 | ### Platform-Specific Considerations 79 | 80 | 1. On iOS, if the user swipes away the application from the app switcher, it must be manually reopened for background messages to work again. 81 | 2. On Android, if the user force-quits the app from device settings, it must be manually reopened for messages to work. 82 | 3. On web, you must have requested a token using `getToken()` with your web push certificate. 83 | 4. For notification messages to display while the app is in the foreground on Android, create a "High Priority" notification channel. 84 | 5. For notification messages to display while the app is in the foreground on iOS, update the presentation options for the application. 85 | 6. For web platforms, create and register a service worker file named `firebase-messaging-sw.js` in your web directory: 86 | ```js 87 | // Please see this file for the latest firebase-js-sdk version: 88 | // https://github.com/firebase/flutterfire/blob/main/packages/firebase_core/firebase_core_web/lib/src/firebase_sdk_version.dart 89 | importScripts("https://www.gstatic.com/firebasejs/10.7.0/firebase-app-compat.js"); 90 | importScripts("https://www.gstatic.com/firebasejs/10.7.0/firebase-messaging-compat.js"); 91 | 92 | firebase.initializeApp({ 93 | apiKey: "...", 94 | authDomain: "...", 95 | databaseURL: "...", 96 | projectId: "...", 97 | storageBucket: "...", 98 | messagingSenderId: "...", 99 | appId: "...", 100 | }); 101 | 102 | const messaging = firebase.messaging(); 103 | 104 | // Optional: 105 | messaging.onBackgroundMessage((message) => { 106 | console.log("onBackgroundMessage", message); 107 | }); 108 | ``` 109 | 110 | ### Token Management 111 | 112 | 1. Retrieve the FCM registration token using `getToken()` to send messages to specific devices. 113 | ```dart 114 | final fcmToken = await FirebaseMessaging.instance.getToken(); 115 | print("FCM Token: $fcmToken"); 116 | ``` 117 | 2. For web platforms, provide your VAPID public key when requesting a token. 118 | ```dart 119 | final fcmToken = await FirebaseMessaging.instance.getToken( 120 | vapidKey: "BKagOny0KF_2pCJQ3m....moL0ewzQ8rZu" 121 | ); 122 | ``` 123 | 3. Subscribe to the `onTokenRefresh` stream to be notified when the token is updated. 124 | ```dart 125 | FirebaseMessaging.instance.onTokenRefresh.listen((fcmToken) { 126 | // Send token to your application server 127 | }).onError((err) { 128 | // Handle error 129 | }); 130 | ``` 131 | 4. For Apple platforms, ensure the APNS token is available before making FCM plugin API calls. 132 | ```dart 133 | final apnsToken = await FirebaseMessaging.instance.getAPNSToken(); 134 | if (apnsToken != null) { 135 | // APNS token is available, make FCM plugin API requests 136 | } 137 | ``` 138 | 139 | ### Auto-Initialization Control 140 | 141 | 1. Disable FCM auto-initialization on iOS by adding metadata to Info.plist. 142 | ``` 143 | FirebaseMessagingAutoInitEnabled = NO 144 | ``` 145 | 2. Disable FCM auto-initialization on Android by adding metadata to AndroidManifest.xml. 146 | ```xml 147 | 150 | 153 | ``` 154 | 3. Re-enable auto-initialization at runtime if needed. 155 | ```dart 156 | await FirebaseMessaging.instance.setAutoInitEnabled(true); 157 | ``` 158 | 4. Be aware that the auto-initialization setting persists across app restarts once set. 159 | -------------------------------------------------------------------------------- /rules/firebase/firebase_remote_config.md: -------------------------------------------------------------------------------- 1 | # Firebase Remote Config Rules 2 | 3 | ### Setup and Configuration 4 | 5 | 1. Install the Firebase Remote Config plugin using `flutter pub add firebase_remote_config`. 6 | 2. Install Firebase Analytics using `flutter pub add firebase_analytics` as it's required for conditional targeting of app instances. 7 | 3. Import the plugin in your Dart code using `import 'package:firebase_remote_config/firebase_remote_config.dart';`. 8 | 4. Enable Google Analytics in your Firebase project for user property and audience targeting. 9 | 5. Ensure the Remote Config REST API is not disabled, as the SDK depends on it. 10 | 6. For macOS, enable Keychain Sharing in Xcode. 11 | 7. Get a Remote Config instance using `final remoteConfig = FirebaseRemoteConfig.instance;`. 12 | 8. Configure Remote Config settings, including fetch timeout and minimum fetch interval. 13 | ```dart 14 | await remoteConfig.setConfigSettings(RemoteConfigSettings( 15 | fetchTimeout: const Duration(minutes: 1), 16 | minimumFetchInterval: const Duration(hours: 1), 17 | )); 18 | ``` 19 | 20 | ### Parameter Management 21 | 22 | 1. Set in-app default parameter values to ensure your app behaves as intended before connecting to the Remote Config backend. 23 | ```dart 24 | await remoteConfig.setDefaults(const { 25 | "example_param_1": 42, 26 | "example_param_2": 3.14159, 27 | "example_param_3": true, 28 | "example_param_4": "Hello, world!", 29 | }); 30 | ``` 31 | 2. Never store confidential data in Remote Config parameter keys or values, as they can be accessed by end users. 32 | 3. Use type-specific getter methods to retrieve parameter values: `getBool()`, `getDouble()`, `getInt()`, and `getString()`. 33 | 4. Define parameters with the same names in the Firebase console as those defined in your app. 34 | 5. Set both default values and conditional values in the Firebase console based on your targeting needs. 35 | 6. Use descriptive parameter names that reflect their purpose in the application. 36 | 7. Group related parameters with common prefixes for better organization (e.g., `login_timeout`, `login_attempts_max`). 37 | 38 | ### Fetching and Activating 39 | 40 | 1. Call `fetch()` to retrieve parameter values from the Remote Config backend. 41 | 2. Call `activate()` to make fetched parameter values available to your app. 42 | 3. Use `fetchAndActivate()` to fetch and make values available in a single call. 43 | ```dart 44 | await remoteConfig.fetchAndActivate(); 45 | ``` 46 | 4. Activate fetched values at appropriate times to ensure a smooth user experience, such as when the app starts. 47 | 5. Check the fetch status using `remoteConfig.lastFetchStatus` to determine if the fetch was successful, failed, or throttled. 48 | 6. Handle fetch failures gracefully by falling back to default values. 49 | 7. Implement error handling for network connectivity issues during fetch operations. 50 | 51 | ### Real-time Updates 52 | 53 | 1. Use real-time Remote Config to listen for updates from the backend (not available for Web). 54 | ```dart 55 | remoteConfig.onConfigUpdated.listen((event) async { 56 | await remoteConfig.activate(); 57 | // Use the new config values here 58 | }); 59 | ``` 60 | 2. Implement proper state management to update your UI when new configuration values are activated. 61 | 3. Consider the impact of real-time updates on app behavior and ensure they don't disrupt the user experience. 62 | 4. Test real-time updates thoroughly to ensure they work as expected across different network conditions. 63 | 64 | ### Throttling and Performance 65 | 66 | 1. Be aware that fetch calls will be throttled if an app fetches too frequently. 67 | 2. The default minimum fetch interval is 12 hours in production. 68 | 3. For development, set a lower minimum fetch interval to facilitate rapid iteration. 69 | ```dart 70 | await remoteConfig.setConfigSettings(RemoteConfigSettings( 71 | fetchTimeout: const Duration(minutes: 1), 72 | minimumFetchInterval: const Duration(minutes: 5), 73 | )); 74 | ``` 75 | 4. Use development settings only in debug builds, not in production. 76 | 5. Be mindful of service-side quota limits when setting fetch intervals, especially with a large user base. 77 | 6. Cache fetched values locally to reduce network requests and improve performance. 78 | 79 | ### Testing and Debugging 80 | 81 | 1. Use conditional values in the Firebase console to test different configurations without deploying new app versions. 82 | 2. Implement A/B testing by creating different parameter values for different user segments. 83 | 3. Log parameter values and fetch status for debugging purposes. 84 | 4. Test your app with both default and remote values to ensure it behaves correctly in all scenarios. 85 | 5. Verify that your app gracefully handles configuration changes during runtime. 86 | 6. Test offline behavior to ensure your app works properly when Remote Config cannot fetch new values. 87 | -------------------------------------------------------------------------------- /rules/firebase/flutterfire_configure.md: -------------------------------------------------------------------------------- 1 | # How to Setup Firebase for Flutter 2 | 3 | ### Setup and Installation 4 | 5 | 1. Install the Firebase CLI using `npm install -g firebase-tools` before starting any Firebase integration with Flutter. 6 | 2. Log into Firebase using `firebase login` to authenticate your development environment. 7 | 3. Install the FlutterFire CLI by running `dart pub global activate flutterfire_cli` from any directory. 8 | 4. Ensure your Flutter app meets minimum platform requirements: API level 19 (KitKat) or higher for Android and iOS 11 or higher for Apple platforms. 9 | 5. Run `flutterfire configure` from your Flutter project directory to set up the Firebase configuration for all platforms. 10 | 6. Re-run `flutterfire configure` any time you add support for a new platform or start using a new Firebase service. 11 | 7. Add the core Firebase package to your app using `flutter pub add firebase_core`. 12 | 13 | ### Configuration and Initialization 14 | 15 | 1. Import both the Firebase core package and the generated configuration file in your main.dart: `import 'package:firebase_core/firebase_core.dart'; import 'firebase_options.dart';`. 16 | 2. Initialize Firebase in your app using the DefaultFirebaseOptions: `await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);`. 17 | 3. Place the Firebase initialization code before any other Firebase service calls, typically in the `main()` function. 18 | 4. Never modify the generated `firebase_options.dart` file manually as it contains auto-generated platform-specific configurations. 19 | 5. For development with Firebase Emulator Suite, use `await Firebase.initializeApp(demoProjectId: "demo-project-id")` instead of the standard initialization. 20 | 6. Keep the `firebase_options.dart` file in version control as it contains non-secret configuration identifiers. 21 | 22 | ### Adding Firebase Services 23 | 24 | 1. Add Firebase plugins to your app using `flutter pub add [plugin_name]` (e.g., `flutter pub add firebase_auth`). 25 | 2. Run `flutterfire configure` after adding any new Firebase plugin to ensure proper configuration. 26 | 3. For Android-specific Firebase services like Crashlytics or Performance Monitoring, the FlutterFire CLI will automatically add required Gradle plugins. 27 | 4. Rebuild your Flutter application using `flutter run` after adding new Firebase plugins. 28 | 5. For multi-platform apps, each Firebase plugin applies to all platforms (iOS, Android, web) unless otherwise specified. 29 | 30 | ### Best Practices 31 | 32 | 1. Follow Firebase project organization best practices when setting up multiple app variants (development, staging, production). 33 | 2. Enable Firebase Analytics in your project for optimal experience with other Firebase products like Crashlytics and Remote Config. 34 | 3. Use a consistent Firebase project across all platforms of your app to ensure data consistency. 35 | 4. For iOS and macOS apps using certain Firebase services, add the Keychain Sharing capability in Xcode. 36 | 5. When using web, ensure you've properly configured your Firebase project for web authentication if using Firebase Auth. 37 | 6. Test your Firebase configuration with both debug and release builds to ensure proper functionality. 38 | 7. For Flutter plugins that use native Firebase SDKs, check version compatibility between the Flutter plugin and the underlying Firebase SDK. 39 | 40 | ### Multiple App Flavors 41 | 42 | 1. Create separate Firebase projects for each environment (development, staging, production) to isolate data and configurations. 43 | 2. Use the `--project` flag with FlutterFire CLI to specify which Firebase project to use for each flavor: 44 | ``` 45 | flutterfire config \ 46 | --project=flutter-app-dev \ 47 | --out=lib/firebase_options_dev.dart \ 48 | --ios-bundle-id=com.example.flutterApp.dev \ 49 | --ios-out=ios/flavors/dev/GoogleService-Info.plist \ 50 | --android-package-name=com.example.flutter_app.dev \ 51 | --android-out=android/app/src/dev/google-services.json 52 | ``` 53 | 3. Specify custom output paths for configuration files using the `--out` flag: `--out=lib/firebase_options_dev.dart` for development flavor. 54 | 4. Configure platform-specific bundle IDs and package names for each flavor using `--ios-bundle-id` and `--android-package-name` flags. 55 | 5. Set custom output paths for platform-specific configuration files with `--ios-out` and `--android-out` flags to match your flavor structure. 56 | 6. Create a helper script to automate the configuration process for multiple flavors, passing the flavor name as a parameter: 57 | ```bash 58 | #!/bin/bash 59 | 60 | # Check if a flavor argument is provided 61 | if [ -z "$1" ]; then 62 | echo "Usage: $0 " 63 | echo "Example: $0 dev" 64 | exit 1 65 | fi 66 | 67 | FLAVOR=$1 68 | PROJECT_ID="flutter-app-$FLAVOR" 69 | 70 | # Run FlutterFire CLI with the appropriate arguments 71 | flutterfire config \ 72 | --project=$PROJECT_ID \ 73 | --out=lib/firebase_options_$FLAVOR.dart \ 74 | --ios-bundle-id=com.example.flutterApp.$FLAVOR \ 75 | --ios-out=ios/flavors/$FLAVOR/GoogleService-Info.plist \ 76 | --android-package-name=com.example.flutter_app.$FLAVOR \ 77 | --android-out=android/app/src/$FLAVOR/google-services.json 78 | ``` 79 | 7. Centralize Firebase initialization logic in a separate file that selects the appropriate configuration based on the current flavor: 80 | ```dart 81 | // firebase.dart 82 | import 'package:firebase_core/firebase_core.dart'; 83 | import 'package:flutter/foundation.dart'; 84 | import 'package:flutter/services.dart'; 85 | import 'package:flutter_app/firebase_options_prod.dart' as prod; 86 | import 'package:flutter_app/firebase_options_stg.dart' as stg; 87 | import 'package:flutter_app/firebase_options_dev.dart' as dev; 88 | 89 | Future initializeFirebaseApp() async { 90 | // Determine which Firebase options to use based on the flavor 91 | final firebaseOptions = switch (appFlavor) { 92 | 'prod' => prod.DefaultFirebaseOptions.currentPlatform, 93 | 'stg' => stg.DefaultFirebaseOptions.currentPlatform, 94 | 'dev' => dev.DefaultFirebaseOptions.currentPlatform, 95 | _ => throw UnsupportedError('Invalid flavor: $appFlavor'), 96 | }; 97 | await Firebase.initializeApp(options: firebaseOptions); 98 | } 99 | ``` 100 | 8. Use the `appFlavor` constant or environment variables to determine which Firebase configuration to use at runtime. 101 | 9. When initializing Firebase with multiple flavors, import each configuration file with namespace aliases: `import 'firebase_options_dev.dart' as dev;`. 102 | 10. Handle flavor selection with a switch statement: `switch (appFlavor) { 'dev' => dev.DefaultFirebaseOptions.currentPlatform, ... }`. 103 | 11. For web applications with multiple flavors, use separate entry points or a centralized initialization approach with conditional logic: 104 | ```dart 105 | // main.dart 106 | import 'firebase.dart'; 107 | 108 | void main() async { 109 | WidgetsFlutterBinding.ensureInitialized(); 110 | await initializeFirebaseApp(); 111 | runApp(const MainApp()); 112 | } 113 | ``` 114 | -------------------------------------------------------------------------------- /rules/flutter_app_architecture.md: -------------------------------------------------------------------------------- 1 | # Flutter App Architecture 2 | 3 | ### Architecture 4 | 1. Separate your features into a UI Layer (presentation), a Data Layer (business data and logic), and, for complex apps, consider adding a Domain (Logic) Layer between UI and Data layers to encapsulate business logic and use-cases. 5 | 2. You can organize code by feature: The classes needed for each feature are grouped together. For example, you might have an auth directory, which would contain files like auth_viewmodel.dart (or, depending on your state management approach: auth_controller.dart, auth_provider.dart, auth_bloc.dart), login_usecase.dart, logout_usecase.dart, login_screen.dart, logout_button.dart, etc. Alternatively, you can organize by type or use a hybrid approach. 6 | 3. Only allow communication between adjacent layers; the UI layer should not access the data layer directly, and vice versa. 7 | 4. Introduce a Logic (Domain) Layer only for complex business logic that does not fit cleanly in the UI or Data layers. 8 | 5. Clearly define the responsibilities, boundaries, and interfaces of each layer and component (Views, View Models, Repositories, Services). 9 | 6. Further divide each layer into components with specific responsibilities and well-defined interfaces. 10 | 7. In the UI Layer, use Views to describe how to present data to the user; keep logic minimal and only UI-related. 11 | 8. Pass events from Views to View Models in response to user interactions. 12 | 9. In View Models, contain logic to convert app data into UI state and maintain the current state needed by the view. 13 | 10. Expose callbacks (commands) from View Models to Views and retrieve/transform data from repositories. 14 | 11. In the Data Layer, use Repositories as the single source of truth (SSOT) for model data and to handle business logic such as caching, error handling, and refreshing data. 15 | 12. Only the SSOT class (usually the repository) should be able to mutate its data; all other classes should read from it. 16 | 13. Repositories should transform raw data from services into domain models and output data consumed by View Models. 17 | 14. Use Services to wrap API endpoints and expose asynchronous response objects; services should isolate data-loading and hold no state. 18 | 15. Use dependency injection to provide components with their dependencies, enabling testability and flexibility. 19 | 20 | ### Data Flow and State 21 | 1. Follow unidirectional data flow: state flows from the data layer through the logic layer to the UI layer, and events from user interaction flow in the opposite direction. 22 | 2. Data changes should always happen in the SSOT (data layer), not in the UI or logic layers. 23 | 3. The UI should always reflect the current (immutable) state; trigger UI rebuilds only in response to state changes. 24 | 4. Views should contain as little logic as possible and be driven by state from View Models. 25 | 26 | ### Use Cases / Interactors 27 | 1. Introduce use cases/interactors in the domain layer only when logic is complex, reused, or merges data from multiple repositories. 28 | 2. Use cases depend on repositories and may be used by multiple view models. 29 | 3. Add use cases only when needed; refactor to use use-cases exclusively if logic is repeatedly shared across view models. 30 | 31 | ### Extensibility and Testability 32 | 1. All architectural components should have well-defined inputs and outputs (interfaces). 33 | 2. Favor dependency injection to allow swapping implementations without changing consumers. 34 | 3. Test view models by mocking repositories; test UI logic independently of widgets. 35 | 4. Design components to be easily replaceable and independently testable. 36 | 37 | ### Best Practices 38 | 1. Strongly recommend following separation of concerns and layered architecture. 39 | 2. Strongly recommend using dependency injection for testability and flexibility. 40 | 3. Recommend using MVVM as the default pattern, but adapt as needed for your app's complexity. 41 | 4. Use key-value storage for simple data (e.g., configuration, preferences) and SQL storage for complex relationships. 42 | 5. Use optimistic updates to improve perceived responsiveness by updating the UI before operations complete. 43 | 6. Support offline-first strategies by combining local and remote data sources in repositories and enabling synchronization as appropriate. 44 | 7. Keep views focused on presentation and extract reusable widgets into separate components. 45 | 8. Use `StatelessWidget` when possible and avoid unnecessary `StatefulWidget`s. 46 | 9. Keep build methods simple and focused on rendering. 47 | 10. Choose state management approaches appropriate to the complexity of your app. 48 | 11. Keep state as local as possible to minimize rebuilds and complexity. 49 | 12. Use `const` constructors when possible to improve performance. 50 | 13. Avoid expensive operations in build methods and implement pagination for large lists. 51 | 14. Keep files focused on a single responsibility and limit file length for readability. 52 | 15. Group related functionality together and use `final` for fields and top-level variables when possible. 53 | 16. Prefer making declarations private and consider making constructors `const` if the class supports it. 54 | 17. Follow Dart naming conventions and format code using `dart format`. 55 | 18. Use curly braces for all flow control statements to ensure clarity and prevent bugs. 56 | 57 | TOTAL CHAR COUNT: 5192 58 | -------------------------------------------------------------------------------- /rules/flutter_change_notifier.md: -------------------------------------------------------------------------------- 1 | ### Flutter ChangeNotifier State Management Rules 2 | 3 | 1. Place shared state above the widgets that use it in the widget tree to enable proper rebuilds and avoid imperative UI updates. 4 | 2. Avoid directly mutating widgets or calling methods on them to change state; instead, rebuild widgets with new data. 5 | 3. Use a model class that extends `ChangeNotifier` to manage and notify listeners of state changes. 6 | ```dart 7 | class CartModel extends ChangeNotifier { 8 | final List _items = []; 9 | UnmodifiableListView get items => UnmodifiableListView(_items); 10 | 11 | void add(Item item) { 12 | _items.add(item); 13 | notifyListeners(); 14 | } 15 | } 16 | ``` 17 | 4. Keep internal state private within the model and expose unmodifiable views to the UI. 18 | 5. Call `notifyListeners()` in your model whenever the state changes to trigger UI rebuilds. 19 | 6. Use `ChangeNotifierProvider` to provide your model to the widget subtree that needs access to it. 20 | ```dart 21 | ChangeNotifierProvider( 22 | create: (context) => CartModel(), 23 | child: MyApp(), 24 | ) 25 | ``` 26 | 7. Wrap widgets that depend on the model’s state in a `Consumer` widget to rebuild only when relevant data changes. 27 | ```dart 28 | return Consumer( 29 | builder: (context, cart, child) => Stack( 30 | children: [ 31 | if (child != null) child, 32 | Text('Total price: \${cart.totalPrice}'), 33 | ], 34 | ), 35 | child: const SomeExpensiveWidget(), 36 | ); 37 | ``` 38 | 8. Always specify the generic type `` for `Consumer` and `Provider.of` to ensure type safety and correct behavior. 39 | 9. Use the `child` parameter of `Consumer` to optimize performance by preventing unnecessary rebuilds of widgets that do not depend on the model. 40 | 10. Place `Consumer` widgets as deep in the widget tree as possible to minimize the scope of rebuilds. 41 | ```dart 42 | return HumongousWidget( 43 | child: AnotherMonstrousWidget( 44 | child: Consumer( 45 | builder: (context, cart, child) { 46 | return Text('Total price: \${cart.totalPrice}'); 47 | }, 48 | ), 49 | ), 50 | ); 51 | ``` 52 | 11. Do not wrap large widget subtrees in a `Consumer` if only a small part depends on the model; instead, wrap only the part that needs to rebuild. 53 | 12. Use `Provider.of(context, listen: false)` when you need to access the model for actions (such as calling methods) but do not want the widget to rebuild on state changes. 54 | ```dart 55 | Provider.of(context, listen: false).removeAll(); 56 | ``` 57 | 13. `ChangeNotifierProvider` automatically disposes of the model when it is no longer needed. 58 | 14. Use `MultiProvider` when you need to provide multiple models to the widget tree. 59 | 15. Write unit tests for your `ChangeNotifier` models to verify state changes and notifications. 60 | 16. Avoid rebuilding widgets unnecessarily; optimize rebuilds by structuring your widget tree and provider usage carefully. 61 | 62 | TOTAL CHAR COUNT: 2752 63 | -------------------------------------------------------------------------------- /rules/flutter_errors.md: -------------------------------------------------------------------------------- 1 | ### Common Flutter Errors 2 | 3 | 1. If you get a "RenderFlex overflowed" error, check if a `Row` or `Column` contains unconstrained widgets. Fix by wrapping children in `Flexible`, `Expanded`, or by setting constraints. 4 | 2. If you get "Vertical viewport was given unbounded height", ensure `ListView` or similar scrollable widgets inside a `Column` have a bounded height (e.g., wrap with `Expanded` or `SizedBox`). 5 | 3. If you get "An InputDecorator...cannot have an unbounded width", constrain the width of widgets like `TextField` using `Expanded`, `SizedBox`, or by placing them in a parent with width constraints. 6 | 4. If you get a "setState called during build" error, do not call `setState` or `showDialog` directly inside the build method. Trigger dialogs or state changes in response to user actions or after the build completes (e.g., using `addPostFrameCallback`). 7 | 5. If you get "The ScrollController is attached to multiple scroll views", make sure each `ScrollController` is only attached to a single scrollable widget at a time. 8 | 6. If you get a "RenderBox was not laid out" error, check for missing or unbounded constraints in your widget tree. This is often caused by using widgets like `ListView` or `Column` without proper size constraints. 9 | 7. Use the Flutter Inspector and review widget constraints to debug layout issues. Refer to the official documentation on constraints if needed. 10 | 11 | TOTAL CHAR COUNT: 1391 12 | -------------------------------------------------------------------------------- /rules/mockito.md: -------------------------------------------------------------------------------- 1 | ### Mockito Rules 2 | 3 | 1. Use a `Fake` when you want a lightweight, custom implementation of a class for testing, especially when you only need to override a subset of methods or provide specific behavior for certain methods. 4 | 2. Use a `Mock` when you need to verify interactions (method calls, arguments, call counts) or when you need to stub method responses dynamically during tests. 5 | 3. Use `@GenerateMocks([YourClass])` or `@GenerateNiceMocks([MockSpec()])` to generate mock classes for your real classes. 6 | 4. Run `flutter pub run build_runner build` or `dart run build_runner build` after adding mock annotations to generate the mock files. 7 | 5. Only annotate files under `test/` for mock generation by default; use a `build.yaml` if you need to generate mocks elsewhere. 8 | 6. Create mock instances from generated classes (e.g., `var mock = MockCat();`). 9 | 7. Use `when(mock.method()).thenReturn(value)` to stub method calls, and `when(mock.method()).thenThrow(error)` to stub errors. 10 | 8. Use `thenAnswer` to calculate a response at call time: `when(mock.method()).thenAnswer((_) => value);`. 11 | 9. Use `thenReturnInOrder([v1, v2])` to return values in sequence for multiple calls. 12 | 10. Always stub methods or getters before interacting with them if you need specific return values. 13 | 11. Use `verify(mock.method())` to check if a method was called; use `verifyNever` to check it was never called. 14 | 12. Use `verify(mock.method()).called(n)` to check the exact number of invocations. 15 | 13. Use argument matchers like `any`, `argThat`, `captureAny`, and `captureThat` for flexible verification and stubbing. 16 | 14. Do not use `null` as an argument adjacent to an argument matcher. 17 | 15. For named arguments, use `any` or `argThat` as values, not as argument names (e.g., `when(mock.method(any, namedArg: any))`). 18 | 16. Use `captureAny` and `captureThat` to capture arguments passed to mocks for later assertions. 19 | 17. Use `untilCalled(mock.method())` to wait for an interaction in async tests. 20 | 18. Understand missing stub behavior: mocks generated with `@GenerateMocks` throw on missing stubs; those with `@GenerateNiceMocks` return a simple legal value. 21 | 19. To mock function types, define an abstract class with the required method signatures and generate mocks for it. 22 | 20. Prefer using real objects over mocks when possible; if not, use a tested fake implementation (`extends Fake`) over a mock. 23 | 21. Never stub out responses in a mock's constructor or within the mock class itself; always stub in your tests. 24 | 22. Never add implementation or `@override` methods to a class extending `Mock`. 25 | 23. Use `reset(mock)` to clear all stubs and interactions; use `clearInteractions(mock)` to clear only interactions. 26 | 24. Use `logInvocations([mock1, mock2])` to print all collected invocations for debugging. 27 | 25. Use `throwOnMissingStub(mock)` to throw if a mock method is called without a matching stub. 28 | 26. Data models should not be mocked if they can be constructed with stubbed data. 29 | 27. Only use mocks if your test asserts on interactions (calls to `verify`); otherwise, prefer real or fake objects. 30 | 31 | TOTAL CHAR COUNT: 3080 32 | -------------------------------------------------------------------------------- /rules/mocktail.md: -------------------------------------------------------------------------------- 1 | ### Mocktail Rules 2 | 3 | 1. Use a `Fake` when you need a lightweight, custom implementation of a class for testing, especially if you only need to override a subset of methods or provide specific behavior. 4 | 2. Use a `Mock` when you need to verify interactions (method calls, arguments, call counts) or need to stub method responses dynamically during tests. 5 | 3. Use `registerFallbackValue` to register a default value for a type that is used as an argument in a mock method, especially when the type is not nullable and is required for argument matching (e.g., `registerFallbackValue(MyCustomEvent())`). 6 | 4. Extend `Mock` to create a mock class for the class or interface you want to mock. 7 | 5. Use `when(() => mock.method()).thenReturn(value)` to stub method calls, and `thenThrow(error)` to stub errors. 8 | 6. Use `when(() => mock.method()).thenAnswer((invocation) => value)` for dynamic responses. 9 | 7. Use `verify(() => mock.method())` to check if a method was called; use `verifyNever(() => mock.method())` to check it was never called. 10 | 8. Use `verify(() => mock.method()).called(n)` to check the exact number of invocations. 11 | 9. Use argument matchers like `any()`, `captureAny()`, and `captureThat()` for flexible verification and stubbing. 12 | 10. Always register fallback values for custom types used with argument matchers before using them in stubs or verifications. 13 | 11. Prefer using real objects over mocks when possible; if not, use a tested fake implementation (`extends Fake`) over a mock. 14 | 12. Never add implementation or `@override` methods to a class extending `Mock`. 15 | 13. Only use mocks if your test asserts on interactions (calls to `verify`); otherwise, prefer real or fake objects. 16 | 14. Always stub async methods (returning `Future` or `Future`) with `thenAnswer((_) async {})` or `thenReturn(Future.value(...))`. 17 | 15. Always include all named parameters in both `when` and `verify` calls, even if you only care about one. Use `any(named: 'paramName')` for those you don't care about. 18 | 16. If a method has default values for named parameters, Mocktail still expects all of them to be matched in both stubs and verifies. 19 | 17. Use `any()` for positional parameters in `when`/`verify` if you don't care about the exact instance. 20 | 18. Register fallback values for any custom types that are used with argument matchers before using them in your tests. 21 | 19. Stub every method you expect to be called, even if it's not the focus of your test, to prevent runtime errors. 22 | 20. When matching string outputs, make sure you understand what `.toString()` returns for the type you are using. 23 | 24 | TOTAL CHAR COUNT: 2577 25 | -------------------------------------------------------------------------------- /rules/provider.md: -------------------------------------------------------------------------------- 1 | ### Provider Rules 2 | 3 | 1. Use `Provider`, `ChangeNotifierProvider`, `FutureProvider`, and `StreamProvider` to expose values and manage state in the widget tree. 4 | 2. Always specify the generic type when using `Provider`, `Consumer`, `context.watch`, `context.read`, or `context.select` for type safety. 5 | ```dart 6 | final value = context.watch(); 7 | ``` 8 | 3. Use `ChangeNotifierProvider` to automatically dispose of the model when it is no longer needed. 9 | ```dart 10 | ChangeNotifierProvider( 11 | create: (_) => MyNotifier(), 12 | child: MyApp(), 13 | ) 14 | ``` 15 | 4. For objects that depend on other providers or values that may change, use `ProxyProvider` or `ChangeNotifierProxyProvider` instead of creating the object from variables that can change over time. 16 | ```dart 17 | ProxyProvider0( 18 | update: (_, __) => MyModel(count), 19 | child: ... 20 | ) 21 | ``` 22 | 5. Use `MultiProvider` to group multiple providers and avoid deeply nested provider trees. 23 | ```dart 24 | MultiProvider( 25 | providers: [ 26 | Provider(create: (_) => Something()), 27 | Provider(create: (_) => SomethingElse()), 28 | ], 29 | child: someWidget, 30 | ) 31 | ``` 32 | 6. Use `context.watch()` to listen to changes and rebuild the widget when `T` changes. 33 | 7. Use `context.read()` to access a provider without listening for changes (e.g., in callbacks). 34 | 8. Use `context.select(R selector(T value))` to listen to only a small part of `T` and optimize rebuilds. 35 | ```dart 36 | final selected = context.select((model) => model.count); 37 | ``` 38 | 9. Use `Consumer` or `Selector` widgets for fine-grained rebuilds when you cannot access a descendant `BuildContext`. 39 | ```dart 40 | Consumer( 41 | builder: (context, value, child) => Text('$value'), 42 | ) 43 | ``` 44 | 10. To migrate from `ValueListenableProvider`, use `Provider` with `ValueListenableBuilder`. 45 | ```dart 46 | ValueListenableBuilder( 47 | valueListenable: myValueListenable, 48 | builder: (context, value, _) { 49 | return Provider.value( 50 | value: value, 51 | child: MyApp(), 52 | ); 53 | } 54 | ) 55 | ``` 56 | 11. Do not create your provider’s object from variables that can change over time; otherwise, the object will not update when the value changes. 57 | 12. For debugging, implement `toString` or use `DiagnosticableTreeMixin` to improve how your objects appear in Flutter DevTools. 58 | ```dart 59 | class MyClass with DiagnosticableTreeMixin { 60 | // ... 61 | @override 62 | String toString() => '$runtimeType(a: $a, b: $b)'; 63 | } 64 | ``` 65 | 13. Do not attempt to obtain providers inside `initState` or `constructor`; use them in `build`, callbacks, or lifecycle methods where the widget is fully mounted. 66 | 14. You can use any object as state, not just `ChangeNotifier`; use `Provider.value()` with a `StatefulWidget` if needed. 67 | 15. If you have a very large number of providers (e.g., 150+), consider mounting them over time (e.g., during splash screen animation) or avoid `MultiProvider` to prevent StackOverflowError. 68 | 69 | TOTAL CHAR COUNT: 2861 70 | -------------------------------------------------------------------------------- /rules/riverpod.md: -------------------------------------------------------------------------------- 1 | # Riverpod Rules 2 | 3 | ### Using Ref in Riverpod 4 | 1. The `Ref` object is essential for accessing the provider system, reading or watching other providers, managing lifecycles, and handling dependencies in Riverpod. 5 | 2. In functional providers, obtain `Ref` as a parameter; in class-based providers, access it as a property of the Notifier. 6 | 3. In widgets, use `WidgetRef` (a subtype of `Ref`) to interact with providers. 7 | 4. The `@riverpod` annotation is used to define providers with code generation, where the function receives `ref` as its parameter. 8 | 5. Use `ref.watch` to reactively listen to other providers; use `ref.read` for one-time access (non-reactive); use `ref.listen` for imperative subscriptions; use `ref.onDispose` to clean up resources. 9 | 6. Example: Functional provider with Ref 10 | ```dart 11 | final otherProvider = Provider((ref) => 0); 12 | final provider = Provider((ref) { 13 | final value = ref.watch(otherProvider); 14 | return value * 2; 15 | }); 16 | ``` 17 | 7. Example: Provider with @riverpod annotation 18 | ```dart 19 | @riverpod 20 | int example(ref) { 21 | return 0; 22 | } 23 | ``` 24 | 8. Example: Using Ref for cleanup 25 | ```dart 26 | final provider = StreamProvider((ref) { 27 | final controller = StreamController(); 28 | ref.onDispose(controller.close); 29 | return controller.stream; 30 | }); 31 | ``` 32 | 9. Example: Using WidgetRef in a widget 33 | ```dart 34 | class MyWidget extends ConsumerWidget { 35 | @override 36 | Widget build(BuildContext context, WidgetRef ref) { 37 | final value = ref.watch(myProvider); 38 | return Text('$value'); 39 | } 40 | } 41 | ``` 42 | 43 | ### Combining Requests 44 | 1. Use the `Ref` object to combine providers and requests; all providers have access to a `Ref`. 45 | 2. In functional providers, obtain `Ref` as a parameter; in class-based providers, access it as a property of the Notifier. 46 | 3. Prefer using `ref.watch` to combine requests, as it enables reactive and declarative logic that automatically recomputes when dependencies change. 47 | 4. When using `ref.watch` with asynchronous providers, use `.future` to await the value if you need the resolved result, otherwise you will receive an `AsyncValue`. 48 | 5. Avoid calling `ref.watch` inside imperative code (e.g., listener callbacks or Notifier methods); only use it during the build phase of the provider. 49 | 6. Use `ref.listen` as an alternative to `ref.watch` for imperative subscriptions, but prefer `ref.watch` for most cases as `ref.listen` is more error-prone. 50 | 7. It is safe to use `ref.listen` during the build phase; listeners are automatically cleaned up when the provider is recomputed. 51 | 8. Use the return value of `ref.listen` to manually remove listeners when needed. 52 | 9. Use `ref.read` only when you cannot use `ref.watch`, such as inside Notifier methods; `ref.read` does not listen to provider changes. 53 | 10. Be cautious with `ref.read`, as providers not being listened to may destroy their state if not actively watched. 54 | 55 | ### Auto Dispose & State Disposal 56 | 1. By default, with code generation, provider state is destroyed when the provider stops being listened to for a full frame. 57 | 2. Opt out of automatic disposal by setting `keepAlive: true` (codegen) or using `ref.keepAlive()` (manual). 58 | 3. When not using code generation, state is not destroyed by default; enable `.autoDispose` on providers to activate automatic disposal. 59 | 4. Always enable automatic disposal for providers that receive parameters to prevent memory leaks from unused parameter combinations. 60 | 5. State is always destroyed when a provider is recomputed, regardless of auto dispose settings. 61 | 6. Use `ref.onDispose` to register cleanup logic that runs when provider state is destroyed; do not trigger side effects or modify providers inside `onDispose`. 62 | 7. Use `ref.onCancel` to react when the last listener is removed, and `ref.onResume` when a new listener is added after cancellation. 63 | 8. Call `ref.onDispose` multiple times if needed—once per disposable object—to ensure all resources are cleaned up. 64 | 9. Use `ref.invalidate` to manually force the destruction of a provider's state; if the provider is still listened to, a new state will be created. 65 | 10. Use `ref.invalidateSelf` inside a provider to force its own destruction and immediate recreation. 66 | 11. When invalidating parameterized providers, you can invalidate a specific parameter or all parameter combinations. 67 | 12. Use `ref.keepAlive` for fine-tuned control over state disposal; revert to automatic disposal using the return value of `ref.keepAlive`. 68 | 13. To keep provider state alive for a specific duration, combine a `Timer` with `ref.keepAlive` and dispose after the timer completes. 69 | 14. Consider using `ref.onCancel` and `ref.onResume` to implement custom disposal strategies, such as delayed disposal after a provider is no longer listened to. 70 | 71 | ### Eager Initialization 72 | 1. Providers are initialized lazily by default; they are only created when first used. 73 | 2. There is no built-in way to mark a provider for eager initialization due to Dart's tree shaking. 74 | 3. To eagerly initialize a provider, explicitly read or watch it at the root of your application (e.g., in a `Consumer` placed directly under `ProviderScope`). 75 | 4. Place the eager initialization logic in a public widget (such as `MyApp`) rather than in `main()` to ensure consistent test behavior. 76 | 5. Eagerly initializing a provider in a dedicated widget will not cause your entire app to rebuild when the provider changes; only the initialization widget will rebuild. 77 | 6. Handle loading and error states for eagerly initialized providers as you would in any `Consumer`, e.g., by returning a loading indicator or error widget. 78 | 7. Use `AsyncValue.requireValue` in widgets to read the data directly and throw a clear exception if the value is not ready, instead of handling loading/error states everywhere. 79 | 8. Avoid creating multiple providers or using overrides solely to hide loading/error states; this adds unnecessary complexity and is discouraged. 80 | 81 | ### First Provider & Network Requests 82 | 1. Always wrap your app with `ProviderScope` at the root (directly in `runApp`) to enable Riverpod for the entire application. 83 | 2. Place business logic such as network requests inside providers; use `Provider`, `FutureProvider`, or `StreamProvider` depending on the return type. 84 | 3. Providers are lazy—network requests or logic inside a provider are only executed when the provider is first read. 85 | 4. Define provider variables as `final` and at the top level (global scope). 86 | 5. Use code generators like Freezed or json_serializable for models and JSON parsing to reduce boilerplate. 87 | 6. Use `Consumer` or `ConsumerWidget` in your UI to access providers via a `ref` object. 88 | 7. Handle loading and error states in the UI by using the `AsyncValue` API returned by `FutureProvider` and `StreamProvider`. 89 | 8. Multiple widgets can listen to the same provider; the provider will only execute once and cache the result. 90 | 9. Use `ConsumerWidget` or `ConsumerStatefulWidget` to reduce code indentation and improve readability over using a `Consumer` widget inside a regular widget. 91 | 10. To use both hooks and providers in the same widget, use `HookConsumerWidget` or `StatefulHookConsumerWidget` from `flutter_hooks` and `hooks_riverpod`. 92 | 11. Always install and use `riverpod_lint` to enable IDE refactoring and enforce best practices. 93 | 12. Do not put `ProviderScope` inside `MyApp`; it must be the top-level widget passed to `runApp`. 94 | 13. When handling network requests, always render loading and error states gracefully in the UI. 95 | 14. Do not re-execute network requests on widget rebuilds; Riverpod ensures the provider is only executed once unless explicitly invalidated. 96 | 97 | ### Passing Arguments to Providers 98 | 1. Use provider "families" to pass arguments to providers; add `.family` after the provider type and specify the argument type. 99 | 2. When using code generation, add parameters directly to the annotated function (excluding `ref`). 100 | 3. Always enable `autoDispose` for providers that receive parameters to avoid memory leaks. 101 | 4. When consuming a provider that takes arguments, call it as a function with the desired parameters (e.g., `ref.watch(myProvider(param))`). 102 | 5. You can listen to the same provider with different arguments simultaneously; each argument combination is cached separately. 103 | 6. The equality (`==`) of provider parameters determines caching—ensure parameters have consistent and correct equality semantics. 104 | 7. Avoid passing objects that do not override `==` (such as plain `List` or `Map`) as provider parameters; use `const` collections, custom classes with proper equality, or Dart 3 records. 105 | 8. Use the `provider_parameters` lint rule from `riverpod_lint` to catch mistakes with parameter equality. 106 | 9. For multiple parameters, prefer code generation or Dart 3 records, as records naturally override `==` and are convenient for grouping arguments. 107 | 10. If two widgets consume the same provider with the same parameters, only one computation/network request is made; with different parameters, each is cached separately. 108 | 109 | ### FAQ & Best Practices 110 | 1. Use `ref.refresh(provider)` when you want to both invalidate a provider and immediately read its new value; use `ref.invalidate(provider)` if you only want to invalidate without reading the value. 111 | 2. Always use the return value of `ref.refresh`; ignoring it will trigger a lint warning. 112 | 3. If a provider is invalidated while not being listened to, it will not update until it is listened to again. 113 | 4. Do not try to share logic between `Ref` and `WidgetRef`; move shared logic into a `Notifier` and call methods on the notifier via `ref.read(yourNotifierProvider.notifier).yourMethod()`. 114 | 5. Prefer `Ref` for business logic and avoid relying on `WidgetRef`, which ties logic to the UI layer. 115 | 6. Extend `ConsumerWidget` instead of using raw `StatelessWidget` when you need access to providers in the widget tree, due to limitations of `InheritedWidget`. 116 | 7. `InheritedWidget` cannot implement a reliable "on change" listener or track when widgets stop listening, which is required for Riverpod's advanced features. 117 | 8. Do not expect to reset all providers at once; instead, make providers that should reset depend on a "user" or "session" provider and reset that dependency. 118 | 9. `hooks_riverpod` and `flutter_hooks` are versioned independently; always add both as dependencies if using hooks. 119 | 10. Riverpod uses `identical` instead of `==` to filter updates for performance reasons, especially with code-generated models; override `updateShouldNotify` on Notifiers to change this behavior. 120 | 11. If you encounter "Cannot use `ref` after the widget was disposed", ensure you check `context.mounted` before using `ref` after an `await` in an async callback. 121 | 122 | ### Provider Observers (Logging & Error Reporting) 123 | 1. Use a `ProviderObserver` to listen to all events in the provider tree for logging, analytics, or error reporting. 124 | 2. Extend the `ProviderObserver` class and override its methods to respond to provider lifecycle events: 125 | - `didAddProvider`: called when a provider is added to the tree. 126 | - `didUpdateProvider`: called when a provider is updated. 127 | - `didDisposeProvider`: called when a provider is disposed. 128 | - `providerDidFail`: called when a synchronous provider throws an error. 129 | 3. Register your observer(s) by passing them to the `observers` parameter of `ProviderScope` (for Flutter apps) or `ProviderContainer` (for pure Dart). 130 | 4. You can register multiple observers if needed by providing a list to the `observers` parameter. 131 | 5. Use observers to integrate with remote error reporting services, log provider state changes, or trigger custom analytics. 132 | 133 | ### Performing Side Effects 134 | 1. Use Notifiers (`Notifier`, `AsyncNotifier`, etc.) to expose methods for performing side effects (e.g., POST, PUT, DELETE) and modifying provider state. 135 | 2. Always define provider variables as `final` and at the top level (global scope). 136 | 3. Choose the provider type (`NotifierProvider`, `AsyncNotifierProvider`, etc.) based on the return type of your logic. 137 | 4. Use provider modifiers like `autoDispose` and `family` as needed for cache management and parameterization. 138 | 5. Expose public methods on Notifiers for UI to trigger state changes or side effects. 139 | 6. In UI event handlers (e.g., button `onPressed`), use `ref.read` to call Notifier methods; avoid using `ref.watch` for imperative actions. 140 | 7. After performing a side effect, update the UI state by: 141 | - Setting the new state directly if the server returns the updated data. 142 | - Calling `ref.invalidateSelf()` to refresh the provider and re-fetch data. 143 | - Manually updating the local cache if the server does not return the new state. 144 | 8. When updating the local cache, prefer immutable state, but mutable state is possible if necessary. 145 | 9. Always handle loading and error states in the UI when performing side effects. 146 | 10. Use progress indicators and error messages to provide feedback for pending or failed operations. 147 | 11. Be aware of the pros and cons of each update approach: 148 | - Direct state update: most up-to-date but depends on server implementation. 149 | - Invalidate and refetch: always consistent with server, but may incur extra network requests. 150 | - Manual cache update: efficient, but risks state divergence from server. 151 | 12. Use hooks (`flutter_hooks`) or `StatefulWidget` to manage local state (e.g., pending futures) for showing spinners or error UI during side effects. 152 | 13. Do not perform side effects directly inside provider constructors or build methods; expose them via Notifier methods and invoke from the UI layer. 153 | 154 | ### Testing Providers 155 | 1. Always create a new `ProviderContainer` (unit tests) or `ProviderScope` (widget tests) for each test to avoid shared state between tests. Use a utility like `createContainer()` to set up and automatically dispose containers (see `/references/riverpod/testing/create_container.dart`). 156 | 2. In unit tests, never share `ProviderContainer` instances between tests. Example: 157 | ```dart 158 | final container = createContainer(); 159 | expect(container.read(provider), equals('some value')); 160 | ``` 161 | 3. In widget tests, always wrap your widget tree with `ProviderScope` when using `tester.pumpWidget`. Example: 162 | ```dart 163 | await tester.pumpWidget( 164 | const ProviderScope(child: YourWidgetYouWantToTest()), 165 | ); 166 | ``` 167 | 4. Obtain a `ProviderContainer` in widget tests using `ProviderScope.containerOf(BuildContext)`. Example: 168 | ```dart 169 | final element = tester.element(find.byType(YourWidgetYouWantToTest)); 170 | final container = ProviderScope.containerOf(element); 171 | ``` 172 | 5. After obtaining the container, you can read or interact with providers as needed for assertions. Example: 173 | ```dart 174 | expect(container.read(provider), 'some value'); 175 | ``` 176 | 6. For providers with `autoDispose`, prefer `container.listen` over `container.read` to prevent the provider's state from being disposed during the test. 177 | 7. Use `container.read` to read provider values and `container.listen` to listen to provider changes in tests. 178 | 8. Use the `overrides` parameter on `ProviderScope` or `ProviderContainer` to inject mocks or fakes for providers in your tests. 179 | 9. Use `container.listen` to spy on changes in a provider for assertions or to combine with mocking libraries. 180 | 10. Await asynchronous providers in tests by reading the `.future` property (for `FutureProvider`) or listening to streams. 181 | 11. Prefer mocking dependencies (such as repositories) used by Notifiers rather than mocking Notifiers directly. 182 | 12. If you must mock a Notifier, subclass the original Notifier base class instead of using `implements` or `with Mock`. 183 | 13. Place Notifier mocks in the same file as the Notifier being mocked if code generation is used, to access generated classes. 184 | 14. Use the `overrides` parameter to swap out Notifiers or providers for mocks or fakes in tests. 185 | 15. Keep all test-specific setup and teardown logic inside the test body or test utility functions. Avoid global state. 186 | 16. Ensure your test environment closely matches your production environment for reliable results. 187 | 188 | TOTAL CHAR COUNT: 15993 189 | --------------------------------------------------------------------------------