@aicgen/aicgen 1.0.0-beta.2 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent/rules/api-design.md +649 -0
- package/.agent/rules/architecture.md +2507 -0
- package/.agent/rules/best-practices.md +622 -0
- package/.agent/rules/code-style.md +308 -0
- package/.agent/rules/design-patterns.md +577 -0
- package/.agent/rules/devops.md +230 -0
- package/.agent/rules/error-handling.md +417 -0
- package/.agent/rules/instructions.md +28 -0
- package/.agent/rules/language.md +786 -0
- package/.agent/rules/performance.md +710 -0
- package/.agent/rules/security.md +587 -0
- package/.agent/rules/testing.md +572 -0
- package/.agent/workflows/add-documentation.md +10 -0
- package/.agent/workflows/generate-integration-tests.md +10 -0
- package/.agent/workflows/generate-unit-tests.md +11 -0
- package/.agent/workflows/performance-audit.md +11 -0
- package/.agent/workflows/refactor-extract-module.md +12 -0
- package/.agent/workflows/security-audit.md +12 -0
- package/.gemini/instructions.md +4843 -0
- package/AGENTS.md +9 -11
- package/bun.lock +755 -4
- package/claude.md +2 -2
- package/config.example.yml +129 -0
- package/config.yml +38 -0
- package/data/guideline-mappings.yml +128 -0
- package/data/language/dart/async.md +289 -0
- package/data/language/dart/basics.md +280 -0
- package/data/language/dart/error-handling.md +355 -0
- package/data/language/dart/index.md +10 -0
- package/data/language/dart/testing.md +352 -0
- package/data/language/swift/basics.md +477 -0
- package/data/language/swift/concurrency.md +654 -0
- package/data/language/swift/error-handling.md +679 -0
- package/data/language/swift/swiftui-mvvm.md +795 -0
- package/data/language/swift/testing.md +708 -0
- package/data/version.json +10 -8
- package/dist/index.js +50295 -29101
- package/jest.config.js +46 -0
- package/package.json +13 -2
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# Dart Fundamentals
|
|
2
|
+
|
|
3
|
+
## Null Safety
|
|
4
|
+
|
|
5
|
+
Dart has sound null safety - non-nullable by default:
|
|
6
|
+
|
|
7
|
+
```dart
|
|
8
|
+
// Non-nullable types
|
|
9
|
+
String name = 'Alice'; // Cannot be null
|
|
10
|
+
int age = 30; // Cannot be null
|
|
11
|
+
|
|
12
|
+
// Nullable types - add ?
|
|
13
|
+
String? middleName; // Can be null
|
|
14
|
+
int? optionalAge; // Can be null
|
|
15
|
+
|
|
16
|
+
// ❌ Compile error
|
|
17
|
+
String lastName = null; // Error: Can't assign null to non-nullable
|
|
18
|
+
|
|
19
|
+
// ✅ Correct
|
|
20
|
+
String? lastName = null; // OK: Explicitly nullable
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Type Annotations
|
|
24
|
+
|
|
25
|
+
Always use explicit types for clarity:
|
|
26
|
+
|
|
27
|
+
```dart
|
|
28
|
+
// ✅ Explicit types
|
|
29
|
+
String getUserName(int userId) {
|
|
30
|
+
final User user = fetchUser(userId);
|
|
31
|
+
return user.name;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Variables
|
|
35
|
+
final String message = 'Hello';
|
|
36
|
+
const int maxRetries = 3;
|
|
37
|
+
List<String> names = ['Alice', 'Bob'];
|
|
38
|
+
|
|
39
|
+
// ❌ Avoid dynamic
|
|
40
|
+
dynamic data = fetchData(); // No type safety
|
|
41
|
+
|
|
42
|
+
// ✅ Use specific types
|
|
43
|
+
Map<String, dynamic> data = fetchData();
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Null-Aware Operators
|
|
47
|
+
|
|
48
|
+
```dart
|
|
49
|
+
// ?. - Null-aware access
|
|
50
|
+
String? name = user?.name; // null if user is null
|
|
51
|
+
|
|
52
|
+
// ?? - Null coalescing
|
|
53
|
+
String displayName = user?.name ?? 'Guest';
|
|
54
|
+
|
|
55
|
+
// ??= - Null-aware assignment
|
|
56
|
+
name ??= 'Unknown'; // Assign only if null
|
|
57
|
+
|
|
58
|
+
// ! - Null assertion (use sparingly)
|
|
59
|
+
String name = user!.name; // Assert user is not null
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Collections
|
|
63
|
+
|
|
64
|
+
```dart
|
|
65
|
+
// Lists
|
|
66
|
+
final List<String> fruits = ['apple', 'banana'];
|
|
67
|
+
fruits.add('orange');
|
|
68
|
+
|
|
69
|
+
// Sets (unique values)
|
|
70
|
+
final Set<int> uniqueIds = {1, 2, 3};
|
|
71
|
+
|
|
72
|
+
// Maps
|
|
73
|
+
final Map<String, int> scores = {
|
|
74
|
+
'Alice': 100,
|
|
75
|
+
'Bob': 95,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// ✅ Use collection if for conditional elements
|
|
79
|
+
final List<String> items = [
|
|
80
|
+
'required',
|
|
81
|
+
if (showOptional) 'optional',
|
|
82
|
+
if (user != null) user.name,
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
// ✅ Use spread operator
|
|
86
|
+
final List<int> combined = [...list1, ...list2];
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Functions
|
|
90
|
+
|
|
91
|
+
```dart
|
|
92
|
+
// Named parameters (recommended for multiple params)
|
|
93
|
+
void createUser({
|
|
94
|
+
required String email,
|
|
95
|
+
required String name,
|
|
96
|
+
int? age,
|
|
97
|
+
}) {
|
|
98
|
+
// email and name are required
|
|
99
|
+
// age is optional
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
createUser(email: 'test@example.com', name: 'Alice');
|
|
103
|
+
|
|
104
|
+
// Positional parameters
|
|
105
|
+
int add(int a, int b) => a + b;
|
|
106
|
+
|
|
107
|
+
// Optional positional
|
|
108
|
+
String greet(String name, [String? title]) {
|
|
109
|
+
return title != null ? '$title $name' : name;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Arrow syntax for single expressions
|
|
113
|
+
bool isAdult(int age) => age >= 18;
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Classes
|
|
117
|
+
|
|
118
|
+
```dart
|
|
119
|
+
// ✅ Use final for immutable fields
|
|
120
|
+
class User {
|
|
121
|
+
final String id;
|
|
122
|
+
final String email;
|
|
123
|
+
String name; // Mutable
|
|
124
|
+
|
|
125
|
+
User({
|
|
126
|
+
required this.id,
|
|
127
|
+
required this.email,
|
|
128
|
+
required this.name,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Named constructors
|
|
132
|
+
User.guest() : id = '', email = '', name = 'Guest';
|
|
133
|
+
|
|
134
|
+
// Factory constructors
|
|
135
|
+
factory User.fromJson(Map<String, dynamic> json) {
|
|
136
|
+
return User(
|
|
137
|
+
id: json['id'] as String,
|
|
138
|
+
email: json['email'] as String,
|
|
139
|
+
name: json['name'] as String,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ✅ Immutable classes with const
|
|
145
|
+
class Point {
|
|
146
|
+
final double x;
|
|
147
|
+
final double y;
|
|
148
|
+
|
|
149
|
+
const Point(this.x, this.y);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const origin = Point(0, 0); // Compile-time constant
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Enums
|
|
156
|
+
|
|
157
|
+
```dart
|
|
158
|
+
// Simple enum
|
|
159
|
+
enum Status {
|
|
160
|
+
pending,
|
|
161
|
+
processing,
|
|
162
|
+
completed,
|
|
163
|
+
failed,
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Enhanced enums (Dart 2.17+)
|
|
167
|
+
enum UserRole {
|
|
168
|
+
admin('Administrator', level: 3),
|
|
169
|
+
editor('Editor', level: 2),
|
|
170
|
+
viewer('Viewer', level: 1);
|
|
171
|
+
|
|
172
|
+
const UserRole(this.displayName, {required this.level});
|
|
173
|
+
|
|
174
|
+
final String displayName;
|
|
175
|
+
final int level;
|
|
176
|
+
|
|
177
|
+
bool canEdit() => level >= 2;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Usage
|
|
181
|
+
final role = UserRole.admin;
|
|
182
|
+
print(role.displayName); // 'Administrator'
|
|
183
|
+
print(role.canEdit()); // true
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Extension Methods
|
|
187
|
+
|
|
188
|
+
```dart
|
|
189
|
+
// Add methods to existing types
|
|
190
|
+
extension StringExtensions on String {
|
|
191
|
+
bool get isEmail => contains('@');
|
|
192
|
+
|
|
193
|
+
String capitalize() {
|
|
194
|
+
if (isEmpty) return this;
|
|
195
|
+
return '${this[0].toUpperCase()}${substring(1)}';
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Usage
|
|
200
|
+
print('test@example.com'.isEmail); // true
|
|
201
|
+
print('hello'.capitalize()); // 'Hello'
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Cascade Notation
|
|
205
|
+
|
|
206
|
+
```dart
|
|
207
|
+
// ✅ Use cascades for fluent method chaining
|
|
208
|
+
final user = User(id: '1', email: 'test@example.com', name: 'Alice')
|
|
209
|
+
..setRole(UserRole.admin)
|
|
210
|
+
..setActive(true)
|
|
211
|
+
..save();
|
|
212
|
+
|
|
213
|
+
// ✅ Building objects
|
|
214
|
+
final button = Button()
|
|
215
|
+
..text = 'Submit'
|
|
216
|
+
..onPressed = handleSubmit
|
|
217
|
+
..enabled = true;
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Late Variables
|
|
221
|
+
|
|
222
|
+
```dart
|
|
223
|
+
// late - initialize later, but before use
|
|
224
|
+
class UserService {
|
|
225
|
+
late final Database db;
|
|
226
|
+
|
|
227
|
+
Future<void> init() async {
|
|
228
|
+
db = await Database.connect();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
Future<User> getUser(String id) async {
|
|
232
|
+
return db.query('SELECT * FROM users WHERE id = ?', [id]);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ⚠️ Runtime error if accessed before initialization
|
|
237
|
+
late String config;
|
|
238
|
+
print(config); // Error!
|
|
239
|
+
|
|
240
|
+
// ✅ Initialize before use
|
|
241
|
+
config = loadConfig();
|
|
242
|
+
print(config); // OK
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Naming Conventions
|
|
246
|
+
|
|
247
|
+
```dart
|
|
248
|
+
// ✓ Classes/Enums/Typedefs: PascalCase
|
|
249
|
+
class UserAccount { }
|
|
250
|
+
enum OrderStatus { }
|
|
251
|
+
typedef IntCallback = void Function(int);
|
|
252
|
+
|
|
253
|
+
// ✓ Variables/Functions/Parameters: camelCase
|
|
254
|
+
String userName = 'Alice';
|
|
255
|
+
void processOrder() { }
|
|
256
|
+
|
|
257
|
+
// ✓ Constants: lowerCamelCase
|
|
258
|
+
const maxRetries = 3;
|
|
259
|
+
const apiBaseUrl = 'https://api.example.com';
|
|
260
|
+
|
|
261
|
+
// ✓ Private members: prefix with _
|
|
262
|
+
class User {
|
|
263
|
+
String _password; // Private field
|
|
264
|
+
void _hashPassword() { } // Private method
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ✓ Files: snake_case
|
|
268
|
+
// user_service.dart
|
|
269
|
+
// order_repository.dart
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Best Practices
|
|
273
|
+
|
|
274
|
+
- Prefer `final` over `var` for immutability
|
|
275
|
+
- Use `const` for compile-time constants
|
|
276
|
+
- Leverage null safety - avoid `!` assertion
|
|
277
|
+
- Use named parameters for functions with multiple parameters
|
|
278
|
+
- Make classes immutable when possible
|
|
279
|
+
- Use extension methods to add functionality to existing types
|
|
280
|
+
- Follow the official Dart style guide
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
# Error Handling in Dart
|
|
2
|
+
|
|
3
|
+
## Custom Exceptions
|
|
4
|
+
|
|
5
|
+
```dart
|
|
6
|
+
// ✅ Create structured exception hierarchy
|
|
7
|
+
class AppException implements Exception {
|
|
8
|
+
final String message;
|
|
9
|
+
final int? code;
|
|
10
|
+
final dynamic details;
|
|
11
|
+
|
|
12
|
+
AppException(this.message, {this.code, this.details});
|
|
13
|
+
|
|
14
|
+
@override
|
|
15
|
+
String toString() => 'AppException: $message (code: $code)';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
class NotFoundException extends AppException {
|
|
19
|
+
NotFoundException(String resource, String id)
|
|
20
|
+
: super('$resource with id $id not found', code: 404);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
class ValidationException extends AppException {
|
|
24
|
+
ValidationException(String message, {Map<String, String>? errors})
|
|
25
|
+
: super(message, code: 400, details: errors);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Usage
|
|
29
|
+
throw NotFoundException('User', userId);
|
|
30
|
+
throw ValidationException('Invalid input', errors: {'email': 'Invalid format'});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Try-Catch-Finally
|
|
34
|
+
|
|
35
|
+
```dart
|
|
36
|
+
// ✅ Catch specific exceptions first
|
|
37
|
+
Future<User> fetchUser(String id) async {
|
|
38
|
+
try {
|
|
39
|
+
final response = await http.get('/api/users/$id');
|
|
40
|
+
return User.fromJson(response.data);
|
|
41
|
+
} on NetworkException catch (e) {
|
|
42
|
+
print('Network error: ${e.message}');
|
|
43
|
+
throw AppException('Network request failed', details: e);
|
|
44
|
+
} on FormatException catch (e) {
|
|
45
|
+
print('Invalid JSON: $e');
|
|
46
|
+
throw AppException('Invalid response format');
|
|
47
|
+
} catch (e, stackTrace) {
|
|
48
|
+
print('Unexpected error: $e');
|
|
49
|
+
print('Stack trace: $stackTrace');
|
|
50
|
+
rethrow;
|
|
51
|
+
} finally {
|
|
52
|
+
print('Request completed');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ✅ Use finally for cleanup
|
|
57
|
+
Future<void> processFile(String path) async {
|
|
58
|
+
File? file;
|
|
59
|
+
try {
|
|
60
|
+
file = File(path);
|
|
61
|
+
final content = await file.readAsString();
|
|
62
|
+
await processContent(content);
|
|
63
|
+
} catch (e) {
|
|
64
|
+
print('Error processing file: $e');
|
|
65
|
+
rethrow;
|
|
66
|
+
} finally {
|
|
67
|
+
// Always runs, even if exception thrown
|
|
68
|
+
await file?.close();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Result Type Pattern
|
|
74
|
+
|
|
75
|
+
```dart
|
|
76
|
+
// ✅ Explicit success/failure without exceptions
|
|
77
|
+
class Result<T> {
|
|
78
|
+
final T? data;
|
|
79
|
+
final AppException? error;
|
|
80
|
+
|
|
81
|
+
const Result.success(T data) : data = data, error = null;
|
|
82
|
+
const Result.failure(AppException error) : data = null, error = error;
|
|
83
|
+
|
|
84
|
+
bool get isSuccess => error == null;
|
|
85
|
+
bool get isFailure => error != null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Usage
|
|
89
|
+
Future<Result<User>> fetchUserSafe(String id) async {
|
|
90
|
+
try {
|
|
91
|
+
final user = await fetchUser(id);
|
|
92
|
+
return Result.success(user);
|
|
93
|
+
} on AppException catch (e) {
|
|
94
|
+
return Result.failure(e);
|
|
95
|
+
} catch (e) {
|
|
96
|
+
return Result.failure(AppException('Unknown error: $e'));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Consumer handles explicitly
|
|
101
|
+
final result = await fetchUserSafe('123');
|
|
102
|
+
if (result.isSuccess) {
|
|
103
|
+
print('User: ${result.data!.name}');
|
|
104
|
+
} else {
|
|
105
|
+
print('Error: ${result.error!.message}');
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Either Type Pattern
|
|
110
|
+
|
|
111
|
+
```dart
|
|
112
|
+
// ✅ Functional error handling
|
|
113
|
+
sealed class Either<L, R> {
|
|
114
|
+
const Either();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
class Left<L, R> extends Either<L, R> {
|
|
118
|
+
final L value;
|
|
119
|
+
const Left(this.value);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
class Right<L, R> extends Either<L, R> {
|
|
123
|
+
final R value;
|
|
124
|
+
const Right(this.value);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Usage
|
|
128
|
+
Future<Either<AppException, User>> fetchUser(String id) async {
|
|
129
|
+
try {
|
|
130
|
+
final user = await _fetchUser(id);
|
|
131
|
+
return Right(user);
|
|
132
|
+
} on AppException catch (e) {
|
|
133
|
+
return Left(e);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Pattern matching (Dart 3.0+)
|
|
138
|
+
final result = await fetchUser('123');
|
|
139
|
+
switch (result) {
|
|
140
|
+
case Left(value: final error):
|
|
141
|
+
print('Error: ${error.message}');
|
|
142
|
+
case Right(value: final user):
|
|
143
|
+
print('User: ${user.name}');
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Validation
|
|
148
|
+
|
|
149
|
+
```dart
|
|
150
|
+
// ✅ Validate early, throw specific errors
|
|
151
|
+
class UserValidator {
|
|
152
|
+
static void validate(String email, String password) {
|
|
153
|
+
if (email.isEmpty) {
|
|
154
|
+
throw ValidationException('Email is required');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (!email.contains('@')) {
|
|
158
|
+
throw ValidationException('Invalid email format');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (password.length < 8) {
|
|
162
|
+
throw ValidationException('Password must be at least 8 characters');
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Usage
|
|
168
|
+
try {
|
|
169
|
+
UserValidator.validate(email, password);
|
|
170
|
+
await createUser(email, password);
|
|
171
|
+
} on ValidationException catch (e) {
|
|
172
|
+
showError(e.message);
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Assert for Development
|
|
177
|
+
|
|
178
|
+
```dart
|
|
179
|
+
// ✅ Use assert for development-time checks
|
|
180
|
+
void transfer(Account from, Account to, double amount) {
|
|
181
|
+
assert(amount > 0, 'Amount must be positive');
|
|
182
|
+
assert(from.balance >= amount, 'Insufficient funds');
|
|
183
|
+
|
|
184
|
+
from.withdraw(amount);
|
|
185
|
+
to.deposit(amount);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Assertions only run in debug mode
|
|
189
|
+
// In production, they're removed
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Error Boundaries (Flutter)
|
|
193
|
+
|
|
194
|
+
```dart
|
|
195
|
+
// ✅ Catch errors at widget level
|
|
196
|
+
class ErrorBoundary extends StatefulWidget {
|
|
197
|
+
final Widget child;
|
|
198
|
+
|
|
199
|
+
const ErrorBoundary({required this.child});
|
|
200
|
+
|
|
201
|
+
@override
|
|
202
|
+
State<ErrorBoundary> createState() => _ErrorBoundaryState();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
class _ErrorBoundaryState extends State<ErrorBoundary> {
|
|
206
|
+
Object? error;
|
|
207
|
+
|
|
208
|
+
@override
|
|
209
|
+
Widget build(BuildContext context) {
|
|
210
|
+
if (error != null) {
|
|
211
|
+
return ErrorWidget(error: error!);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return ErrorWrapper(
|
|
215
|
+
onError: (error, stackTrace) {
|
|
216
|
+
setState(() => this.error = error);
|
|
217
|
+
},
|
|
218
|
+
child: widget.child,
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Global Error Handling
|
|
225
|
+
|
|
226
|
+
```dart
|
|
227
|
+
// ✅ Catch uncaught errors
|
|
228
|
+
void main() {
|
|
229
|
+
// Synchronous errors
|
|
230
|
+
FlutterError.onError = (details) {
|
|
231
|
+
print('Flutter error: ${details.exception}');
|
|
232
|
+
print('Stack trace: ${details.stack}');
|
|
233
|
+
reportError(details.exception, details.stack);
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// Asynchronous errors
|
|
237
|
+
PlatformDispatcher.instance.onError = (error, stack) {
|
|
238
|
+
print('Async error: $error');
|
|
239
|
+
reportError(error, stack);
|
|
240
|
+
return true;
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
runApp(MyApp());
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
void reportError(Object error, StackTrace? stackTrace) {
|
|
247
|
+
// Send to error tracking service
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Retry Pattern
|
|
252
|
+
|
|
253
|
+
```dart
|
|
254
|
+
// ✅ Retry with exponential backoff
|
|
255
|
+
Future<T> retryWithBackoff<T>(
|
|
256
|
+
Future<T> Function() operation, {
|
|
257
|
+
int maxAttempts = 3,
|
|
258
|
+
Duration initialDelay = const Duration(seconds: 1),
|
|
259
|
+
}) async {
|
|
260
|
+
int attempt = 0;
|
|
261
|
+
while (true) {
|
|
262
|
+
try {
|
|
263
|
+
return await operation();
|
|
264
|
+
} catch (e) {
|
|
265
|
+
attempt++;
|
|
266
|
+
if (attempt >= maxAttempts) rethrow;
|
|
267
|
+
|
|
268
|
+
final delay = initialDelay * (1 << attempt); // Exponential backoff
|
|
269
|
+
print('Retry attempt $attempt after $delay');
|
|
270
|
+
await Future.delayed(delay);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Usage
|
|
276
|
+
final user = await retryWithBackoff(
|
|
277
|
+
() => fetchUser('123'),
|
|
278
|
+
maxAttempts: 5,
|
|
279
|
+
);
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Circuit Breaker Pattern
|
|
283
|
+
|
|
284
|
+
```dart
|
|
285
|
+
// ✅ Prevent cascading failures
|
|
286
|
+
class CircuitBreaker {
|
|
287
|
+
final int failureThreshold;
|
|
288
|
+
final Duration timeout;
|
|
289
|
+
|
|
290
|
+
int _failureCount = 0;
|
|
291
|
+
DateTime? _openedAt;
|
|
292
|
+
|
|
293
|
+
CircuitBreaker({
|
|
294
|
+
this.failureThreshold = 5,
|
|
295
|
+
this.timeout = const Duration(minutes: 1),
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
Future<T> execute<T>(Future<T> Function() operation) async {
|
|
299
|
+
if (_isOpen()) {
|
|
300
|
+
if (_shouldReset()) {
|
|
301
|
+
_reset();
|
|
302
|
+
} else {
|
|
303
|
+
throw AppException('Circuit breaker is open');
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
try {
|
|
308
|
+
final result = await operation();
|
|
309
|
+
_onSuccess();
|
|
310
|
+
return result;
|
|
311
|
+
} catch (e) {
|
|
312
|
+
_onFailure();
|
|
313
|
+
rethrow;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
bool _isOpen() => _openedAt != null;
|
|
318
|
+
|
|
319
|
+
bool _shouldReset() {
|
|
320
|
+
return _openedAt != null &&
|
|
321
|
+
DateTime.now().difference(_openedAt!) > timeout;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
void _onSuccess() {
|
|
325
|
+
_failureCount = 0;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
void _onFailure() {
|
|
329
|
+
_failureCount++;
|
|
330
|
+
if (_failureCount >= failureThreshold) {
|
|
331
|
+
_openedAt = DateTime.now();
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
void _reset() {
|
|
336
|
+
_failureCount = 0;
|
|
337
|
+
_openedAt = null;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
## Best Practices
|
|
343
|
+
|
|
344
|
+
- Create custom exception classes for different error types
|
|
345
|
+
- Catch specific exceptions before general ones
|
|
346
|
+
- Use `rethrow` to preserve stack traces
|
|
347
|
+
- Always clean up resources in `finally` blocks
|
|
348
|
+
- Validate input early and throw meaningful errors
|
|
349
|
+
- Use Result/Either types for expected failures
|
|
350
|
+
- Implement global error handlers for uncaught errors
|
|
351
|
+
- Use retry with exponential backoff for transient failures
|
|
352
|
+
- Implement circuit breakers for external service calls
|
|
353
|
+
- Don't catch exceptions you can't handle
|
|
354
|
+
- Include context in error messages
|
|
355
|
+
- Log errors with stack traces
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Dart Language Guidelines
|
|
2
|
+
|
|
3
|
+
Comprehensive guidelines for writing idiomatic Dart code.
|
|
4
|
+
|
|
5
|
+
## Topics
|
|
6
|
+
|
|
7
|
+
- [Fundamentals](basics.md) - Types, null safety, syntax
|
|
8
|
+
- [Async Programming](async.md) - Futures, async/await, Streams
|
|
9
|
+
- [Error Handling](error-handling.md) - Exceptions and error patterns
|
|
10
|
+
- [Testing](testing.md) - Unit and widget testing
|