├── 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 |
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 |

54 |

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 |
--------------------------------------------------------------------------------