openskill-ai 1.0.0
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/LICENSE +21 -0
- package/README.md +230 -0
- package/dist/chunk-63EFN7CX.js +450 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +286 -0
- package/dist/index.d.ts +158 -0
- package/dist/index.js +391 -0
- package/package.json +70 -0
- package/skills/backend-best-practices/SKILL.md +116 -0
- package/skills/backend-best-practices/rules/api-consistent-responses.md +127 -0
- package/skills/backend-best-practices/rules/api-pagination.md +83 -0
- package/skills/backend-best-practices/rules/api-rate-limiting.md +94 -0
- package/skills/backend-best-practices/rules/api-restful-conventions.md +67 -0
- package/skills/backend-best-practices/rules/api-versioning.md +69 -0
- package/skills/backend-best-practices/rules/arch-dependency-injection.md +55 -0
- package/skills/backend-best-practices/rules/arch-dto-pattern.md +64 -0
- package/skills/backend-best-practices/rules/arch-repository-pattern.md +74 -0
- package/skills/backend-best-practices/rules/arch-separation-concerns.md +80 -0
- package/skills/backend-best-practices/rules/arch-service-layer.md +48 -0
- package/skills/backend-best-practices/rules/code-documentation.md +77 -0
- package/skills/backend-best-practices/rules/code-dry-principle.md +49 -0
- package/skills/backend-best-practices/rules/code-naming-conventions.md +47 -0
- package/skills/backend-best-practices/rules/code-single-responsibility.md +78 -0
- package/skills/backend-best-practices/rules/code-type-safety.md +64 -0
- package/skills/backend-best-practices/rules/db-connection-pooling.md +136 -0
- package/skills/backend-best-practices/rules/db-indexing.md +88 -0
- package/skills/backend-best-practices/rules/db-migrations.md +189 -0
- package/skills/backend-best-practices/rules/db-n-plus-one.md +118 -0
- package/skills/backend-best-practices/rules/db-transactions.md +178 -0
- package/skills/backend-best-practices/rules/deploy-environment-variables.md +63 -0
- package/skills/backend-best-practices/rules/deploy-graceful-shutdown.md +77 -0
- package/skills/backend-best-practices/rules/deploy-health-checks.md +70 -0
- package/skills/backend-best-practices/rules/deploy-monitoring.md +87 -0
- package/skills/backend-best-practices/rules/deploy-zero-downtime.md +85 -0
- package/skills/backend-best-practices/rules/error-global-handler.md +94 -0
- package/skills/backend-best-practices/rules/error-graceful-degradation.md +70 -0
- package/skills/backend-best-practices/rules/error-http-status-codes.md +77 -0
- package/skills/backend-best-practices/rules/error-logging.md +71 -0
- package/skills/backend-best-practices/rules/error-meaningful-messages.md +61 -0
- package/skills/backend-best-practices/rules/perf-async-operations.md +55 -0
- package/skills/backend-best-practices/rules/perf-caching.md +81 -0
- package/skills/backend-best-practices/rules/perf-compression.md +33 -0
- package/skills/backend-best-practices/rules/perf-database-queries.md +54 -0
- package/skills/backend-best-practices/rules/perf-lazy-loading.md +47 -0
- package/skills/backend-best-practices/rules/security-https-only.md +116 -0
- package/skills/backend-best-practices/rules/security-input-validation.md +96 -0
- package/skills/backend-best-practices/rules/security-jwt-best-practices.md +140 -0
- package/skills/backend-best-practices/rules/security-sql-injection.md +77 -0
- package/skills/clean-code-skills/references/solid.md +304 -0
- package/skills/clean-code-skills/skills.md +263 -0
- package/skills/flutter-skills/AGENTS.md +1265 -0
- package/skills/flutter-skills/SKILL.md +116 -0
- package/skills/flutter-skills/rules/advanced-custom-painter.md +117 -0
- package/skills/flutter-skills/rules/advanced-layer-link.md +103 -0
- package/skills/flutter-skills/rules/advanced-render-object.md +105 -0
- package/skills/flutter-skills/rules/advanced-sliver-persistent.md +111 -0
- package/skills/flutter-skills/rules/animation-animated-builder.md +118 -0
- package/skills/flutter-skills/rules/animation-cached-images.md +112 -0
- package/skills/flutter-skills/rules/animation-physics.md +105 -0
- package/skills/flutter-skills/rules/animation-reduce-overdraw.md +111 -0
- package/skills/flutter-skills/rules/animation-tween-sequence.md +112 -0
- package/skills/flutter-skills/rules/async-cancel-subscriptions.md +112 -0
- package/skills/flutter-skills/rules/async-compute.md +78 -0
- package/skills/flutter-skills/rules/async-debounce-throttle.md +104 -0
- package/skills/flutter-skills/rules/async-future-builder.md +106 -0
- package/skills/flutter-skills/rules/async-parallel.md +75 -0
- package/skills/flutter-skills/rules/build-avoid-rebuild.md +80 -0
- package/skills/flutter-skills/rules/build-const-constructors.md +56 -0
- package/skills/flutter-skills/rules/build-itemextent.md +73 -0
- package/skills/flutter-skills/rules/build-keys.md +74 -0
- package/skills/flutter-skills/rules/build-split-widgets.md +99 -0
- package/skills/flutter-skills/rules/dart-avoid-dynamic.md +86 -0
- package/skills/flutter-skills/rules/dart-cascade-notation.md +89 -0
- package/skills/flutter-skills/rules/dart-collection-if.md +92 -0
- package/skills/flutter-skills/rules/dart-final-const.md +70 -0
- package/skills/flutter-skills/rules/dart-spread-operator.md +90 -0
- package/skills/flutter-skills/rules/dart-string-buffer.md +77 -0
- package/skills/flutter-skills/rules/layout-avoid-opacity.md +110 -0
- package/skills/flutter-skills/rules/layout-clip-behavior.md +94 -0
- package/skills/flutter-skills/rules/layout-intrinsic-dimensions.md +89 -0
- package/skills/flutter-skills/rules/layout-repaint-boundary.md +117 -0
- package/skills/flutter-skills/rules/layout-slivers.md +94 -0
- package/skills/flutter-skills/rules/memory-dispose.md +90 -0
- package/skills/flutter-skills/rules/memory-image-cache.md +86 -0
- package/skills/flutter-skills/rules/memory-isolate.md +91 -0
- package/skills/flutter-skills/rules/memory-precache.md +114 -0
- package/skills/flutter-skills/rules/memory-weak-references.md +79 -0
- package/skills/flutter-skills/rules/state-late-final.md +90 -0
- package/skills/flutter-skills/rules/state-lift-state-up.md +84 -0
- package/skills/flutter-skills/rules/state-minimize-rebuilds.md +95 -0
- package/skills/flutter-skills/rules/state-selector.md +87 -0
- package/skills/flutter-skills/rules/state-valuenotifier.md +85 -0
- package/skills/frontend-design/SKILL.md +42 -0
- package/skills/skill-writer-skills/AGENTS.md +637 -0
- package/skills/skill-writer-skills/README.md +49 -0
- package/skills/skill-writer-skills/SKILL.md +97 -0
- package/skills/skill-writer-skills/metadata.json +17 -0
- package/skills/skill-writer-skills/references/common-pitfalls.md +291 -0
- package/skills/skill-writer-skills/references/core-principles.md +147 -0
- package/skills/skill-writer-skills/references/creation-process.md +250 -0
- package/skills/skill-writer-skills/references/design-patterns.md +300 -0
- package/skills/skill-writer-skills/references/skill-anatomy.md +174 -0
- package/skills/skill-writer-skills/references/validation-checklist.md +194 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Avoid Dynamic Types
|
|
3
|
+
impact: LOW-MEDIUM
|
|
4
|
+
impactDescription: Enables AOT optimization, catches errors
|
|
5
|
+
tags: dart, types, dynamic, safety
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Avoid Dynamic Types
|
|
9
|
+
|
|
10
|
+
Avoid `dynamic` type to enable compiler optimizations and catch errors at compile time.
|
|
11
|
+
|
|
12
|
+
**Incorrect (dynamic defeats type checking):**
|
|
13
|
+
|
|
14
|
+
```dart
|
|
15
|
+
dynamic fetchData() {
|
|
16
|
+
// Caller has no idea what type to expect
|
|
17
|
+
return {'name': 'John', 'age': 30};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
void processData(dynamic data) {
|
|
21
|
+
// No autocomplete, no type safety
|
|
22
|
+
print(data.name); // Runtime error if wrong type
|
|
23
|
+
print(data.nonExistent); // No compile-time error!
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
List<dynamic> items = [1, 'two', 3.0]; // Mixed types
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Correct (explicit types):**
|
|
30
|
+
|
|
31
|
+
```dart
|
|
32
|
+
Map<String, dynamic> fetchData() {
|
|
33
|
+
return {'name': 'John', 'age': 30};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Better: Use a typed model
|
|
37
|
+
class User {
|
|
38
|
+
final String name;
|
|
39
|
+
final int age;
|
|
40
|
+
|
|
41
|
+
const User({required this.name, required this.age});
|
|
42
|
+
|
|
43
|
+
factory User.fromJson(Map<String, dynamic> json) {
|
|
44
|
+
return User(
|
|
45
|
+
name: json['name'] as String,
|
|
46
|
+
age: json['age'] as int,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
User fetchUser() {
|
|
52
|
+
final json = {'name': 'John', 'age': 30};
|
|
53
|
+
return User.fromJson(json);
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**For JSON handling:**
|
|
58
|
+
|
|
59
|
+
```dart
|
|
60
|
+
// Accept dynamic at API boundary, convert immediately
|
|
61
|
+
class ApiService {
|
|
62
|
+
Future<User> getUser(int id) async {
|
|
63
|
+
final response = await http.get('/users/$id');
|
|
64
|
+
final json = jsonDecode(response.body) as Map<String, dynamic>;
|
|
65
|
+
return User.fromJson(json); // Typed from here on
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**When dynamic is acceptable:**
|
|
71
|
+
- JSON parsing at boundaries
|
|
72
|
+
- Reflection/mirrors (rare)
|
|
73
|
+
- Interop with JavaScript
|
|
74
|
+
|
|
75
|
+
**Enable strict linting:**
|
|
76
|
+
|
|
77
|
+
```yaml
|
|
78
|
+
# analysis_options.yaml
|
|
79
|
+
analyzer:
|
|
80
|
+
language:
|
|
81
|
+
strict-casts: true
|
|
82
|
+
strict-raw-types: true
|
|
83
|
+
linter:
|
|
84
|
+
rules:
|
|
85
|
+
- avoid_dynamic_calls
|
|
86
|
+
```
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use Cascade Notation for Multiple Operations
|
|
3
|
+
impact: LOW-MEDIUM
|
|
4
|
+
impactDescription: Cleaner code, fewer intermediate variables
|
|
5
|
+
tags: dart, cascade, notation, chaining
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use Cascade Notation for Multiple Operations
|
|
9
|
+
|
|
10
|
+
Use cascade notation (`..`) to perform multiple operations on the same object without repeating the reference.
|
|
11
|
+
|
|
12
|
+
**Incorrect (repeated object reference):**
|
|
13
|
+
|
|
14
|
+
```dart
|
|
15
|
+
void setupController() {
|
|
16
|
+
final controller = TextEditingController();
|
|
17
|
+
controller.text = 'Initial value';
|
|
18
|
+
controller.selection = TextSelection.collapsed(offset: 0);
|
|
19
|
+
controller.addListener(onChanged);
|
|
20
|
+
return controller;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Or with builder pattern feeling clunky:
|
|
24
|
+
void buildRequest() {
|
|
25
|
+
final request = HttpRequest();
|
|
26
|
+
request.method = 'POST';
|
|
27
|
+
request.headers['Content-Type'] = 'application/json';
|
|
28
|
+
request.body = jsonEncode(data);
|
|
29
|
+
return request.send();
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Correct (cascade notation):**
|
|
34
|
+
|
|
35
|
+
```dart
|
|
36
|
+
TextEditingController setupController() {
|
|
37
|
+
return TextEditingController()
|
|
38
|
+
..text = 'Initial value'
|
|
39
|
+
..selection = const TextSelection.collapsed(offset: 0)
|
|
40
|
+
..addListener(onChanged);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
Future<Response> buildRequest() {
|
|
44
|
+
return (HttpRequest()
|
|
45
|
+
..method = 'POST'
|
|
46
|
+
..headers['Content-Type'] = 'application/json'
|
|
47
|
+
..body = jsonEncode(data))
|
|
48
|
+
.send();
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Common use cases:**
|
|
53
|
+
|
|
54
|
+
```dart
|
|
55
|
+
// Paint configuration
|
|
56
|
+
final paint = Paint()
|
|
57
|
+
..color = Colors.blue
|
|
58
|
+
..strokeWidth = 2.0
|
|
59
|
+
..style = PaintingStyle.stroke
|
|
60
|
+
..strokeCap = StrokeCap.round;
|
|
61
|
+
|
|
62
|
+
// Path building
|
|
63
|
+
final path = Path()
|
|
64
|
+
..moveTo(0, 0)
|
|
65
|
+
..lineTo(100, 0)
|
|
66
|
+
..lineTo(100, 100)
|
|
67
|
+
..close();
|
|
68
|
+
|
|
69
|
+
// List population
|
|
70
|
+
final items = <String>[]
|
|
71
|
+
..add('first')
|
|
72
|
+
..addAll(['second', 'third'])
|
|
73
|
+
..sort();
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Null-aware cascade (Dart 2.12+):**
|
|
77
|
+
|
|
78
|
+
```dart
|
|
79
|
+
// Only cascades if object is not null
|
|
80
|
+
paint
|
|
81
|
+
?..color = Colors.red
|
|
82
|
+
..strokeWidth = 3.0;
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Benefits:**
|
|
86
|
+
- Less repetition
|
|
87
|
+
- Cleaner initialization
|
|
88
|
+
- Returns the object (unlike method calls)
|
|
89
|
+
- Works great with builders
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use Collection-if Instead of Conditional Logic
|
|
3
|
+
impact: LOW-MEDIUM
|
|
4
|
+
impactDescription: Cleaner, more performant collections
|
|
5
|
+
tags: dart, collection, conditional, syntax
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use Collection-if Instead of Conditional Logic
|
|
9
|
+
|
|
10
|
+
Use collection-if and collection-for for cleaner dynamic lists with better performance.
|
|
11
|
+
|
|
12
|
+
**Incorrect (imperative approach):**
|
|
13
|
+
|
|
14
|
+
```dart
|
|
15
|
+
List<Widget> buildItems(bool showHeader, bool showFooter) {
|
|
16
|
+
var items = <Widget>[];
|
|
17
|
+
|
|
18
|
+
if (showHeader) {
|
|
19
|
+
items.add(HeaderWidget());
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
items.add(MainContent());
|
|
23
|
+
|
|
24
|
+
if (showFooter) {
|
|
25
|
+
items.add(FooterWidget());
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return items;
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Correct (collection-if):**
|
|
33
|
+
|
|
34
|
+
```dart
|
|
35
|
+
List<Widget> buildItems(bool showHeader, bool showFooter) {
|
|
36
|
+
return [
|
|
37
|
+
if (showHeader) const HeaderWidget(),
|
|
38
|
+
const MainContent(),
|
|
39
|
+
if (showFooter) const FooterWidget(),
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**In widget trees:**
|
|
45
|
+
|
|
46
|
+
```dart
|
|
47
|
+
@override
|
|
48
|
+
Widget build(BuildContext context) {
|
|
49
|
+
return Column(
|
|
50
|
+
children: [
|
|
51
|
+
const TitleWidget(),
|
|
52
|
+
if (isLoading) const CircularProgressIndicator(),
|
|
53
|
+
if (hasError) Text('Error: $errorMessage'),
|
|
54
|
+
if (data != null) ...[
|
|
55
|
+
DataHeader(data: data!),
|
|
56
|
+
for (final item in data!.items) ItemWidget(item: item),
|
|
57
|
+
],
|
|
58
|
+
if (showActions) const ActionButtons(),
|
|
59
|
+
],
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Collection-for:**
|
|
65
|
+
|
|
66
|
+
```dart
|
|
67
|
+
// Incorrect
|
|
68
|
+
List<Widget> buildList(List<Item> items) {
|
|
69
|
+
return items.map((item) => ItemWidget(item: item)).toList();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Correct (collection-for)
|
|
73
|
+
List<Widget> buildList(List<Item> items) {
|
|
74
|
+
return [
|
|
75
|
+
for (final item in items) ItemWidget(item: item),
|
|
76
|
+
];
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Combined usage:**
|
|
81
|
+
|
|
82
|
+
```dart
|
|
83
|
+
final menu = [
|
|
84
|
+
const MenuItem(title: 'Home'),
|
|
85
|
+
if (isLoggedIn) ...[
|
|
86
|
+
const MenuItem(title: 'Profile'),
|
|
87
|
+
for (final bookmark in bookmarks)
|
|
88
|
+
MenuItem(title: bookmark.name),
|
|
89
|
+
],
|
|
90
|
+
if (isAdmin) const MenuItem(title: 'Admin'),
|
|
91
|
+
];
|
|
92
|
+
```
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use final and const Appropriately
|
|
3
|
+
impact: LOW-MEDIUM
|
|
4
|
+
impactDescription: Enables compiler optimizations
|
|
5
|
+
tags: dart, final, const, immutability
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use final and const Appropriately
|
|
9
|
+
|
|
10
|
+
Use `final` for runtime constants and `const` for compile-time constants to enable optimizations.
|
|
11
|
+
|
|
12
|
+
**Incorrect (no immutability hints):**
|
|
13
|
+
|
|
14
|
+
```dart
|
|
15
|
+
class BadExample {
|
|
16
|
+
var name = 'John'; // Mutable - can change
|
|
17
|
+
var items = <String>[]; // Mutable list
|
|
18
|
+
var config = {'key': 'value'}; // Mutable map
|
|
19
|
+
|
|
20
|
+
void process() {
|
|
21
|
+
var result = calculateSomething(); // Not marked final
|
|
22
|
+
print(result);
|
|
23
|
+
// result could accidentally be reassigned
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Correct (proper const/final usage):**
|
|
29
|
+
|
|
30
|
+
```dart
|
|
31
|
+
class GoodExample {
|
|
32
|
+
final String name = 'John'; // Runtime immutable
|
|
33
|
+
final List<String> items; // Reference immutable
|
|
34
|
+
|
|
35
|
+
// Compile-time constant
|
|
36
|
+
static const defaultConfig = {'key': 'value'};
|
|
37
|
+
|
|
38
|
+
// Const constructor enables const instances
|
|
39
|
+
const GoodExample({required this.items});
|
|
40
|
+
|
|
41
|
+
void process() {
|
|
42
|
+
final result = calculateSomething(); // Cannot reassign
|
|
43
|
+
print(result);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Usage
|
|
48
|
+
const instance = GoodExample(items: ['a', 'b']); // Compile-time constant
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**const collections:**
|
|
52
|
+
|
|
53
|
+
```dart
|
|
54
|
+
// Incorrect - creates new list each time
|
|
55
|
+
Widget build(BuildContext context) {
|
|
56
|
+
return Row(children: [Text('A'), Text('B')]);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Correct - reuses same list
|
|
60
|
+
Widget build(BuildContext context) {
|
|
61
|
+
return const Row(children: [Text('A'), Text('B')]);
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Guidelines:**
|
|
66
|
+
- Use `const` for values known at compile time
|
|
67
|
+
- Use `final` for values computed at runtime but never changed
|
|
68
|
+
- Prefer `const` constructors in widgets
|
|
69
|
+
- Use `const` for default parameter values
|
|
70
|
+
- Enable `prefer_const_declarations` lint
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use Spread Operator for Collection Merging
|
|
3
|
+
impact: LOW-MEDIUM
|
|
4
|
+
impactDescription: More efficient collection operations
|
|
5
|
+
tags: dart, spread, collections, merge
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use Spread Operator for Collection Merging
|
|
9
|
+
|
|
10
|
+
Use the spread operator (`...`) for efficient collection merging instead of addAll or concatenation.
|
|
11
|
+
|
|
12
|
+
**Incorrect (multiple operations):**
|
|
13
|
+
|
|
14
|
+
```dart
|
|
15
|
+
List<int> combineNumbers() {
|
|
16
|
+
var result = <int>[];
|
|
17
|
+
result.addAll([1, 2, 3]);
|
|
18
|
+
result.add(4);
|
|
19
|
+
result.addAll([5, 6, 7]);
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Or inefficient concatenation
|
|
24
|
+
List<int> combineNumbers() {
|
|
25
|
+
return [1, 2, 3] + [4] + [5, 6, 7]; // Creates intermediate lists
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Correct (spread operator):**
|
|
30
|
+
|
|
31
|
+
```dart
|
|
32
|
+
List<int> combineNumbers() {
|
|
33
|
+
return [...[1, 2, 3], 4, ...[5, 6, 7]];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Or more readable:
|
|
37
|
+
List<int> combineNumbers() {
|
|
38
|
+
const first = [1, 2, 3];
|
|
39
|
+
const last = [5, 6, 7];
|
|
40
|
+
return [...first, 4, ...last];
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Null-aware spread:**
|
|
45
|
+
|
|
46
|
+
```dart
|
|
47
|
+
List<Widget> buildWidgets(List<Widget>? extraWidgets) {
|
|
48
|
+
return [
|
|
49
|
+
const Header(),
|
|
50
|
+
...?extraWidgets, // Only spreads if not null
|
|
51
|
+
const Footer(),
|
|
52
|
+
];
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**With maps:**
|
|
57
|
+
|
|
58
|
+
```dart
|
|
59
|
+
Map<String, dynamic> mergeConfig(Map<String, dynamic> overrides) {
|
|
60
|
+
return {
|
|
61
|
+
'theme': 'dark',
|
|
62
|
+
'fontSize': 14,
|
|
63
|
+
...overrides, // Overrides default values
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**In widget builders:**
|
|
69
|
+
|
|
70
|
+
```dart
|
|
71
|
+
@override
|
|
72
|
+
Widget build(BuildContext context) {
|
|
73
|
+
return Row(
|
|
74
|
+
children: [
|
|
75
|
+
const LeadingIcon(),
|
|
76
|
+
...items.map((item) => ItemWidget(item: item)),
|
|
77
|
+
if (showTrailing) ...[
|
|
78
|
+
const Spacer(),
|
|
79
|
+
const TrailingIcon(),
|
|
80
|
+
],
|
|
81
|
+
],
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Benefits:
|
|
87
|
+
- Single list creation
|
|
88
|
+
- More readable
|
|
89
|
+
- Works with const
|
|
90
|
+
- Null-safety with `...?`
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use StringBuffer for String Concatenation
|
|
3
|
+
impact: LOW-MEDIUM
|
|
4
|
+
impactDescription: O(n) vs O(n²) for large strings
|
|
5
|
+
tags: dart, string, buffer, performance
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use StringBuffer for String Concatenation
|
|
9
|
+
|
|
10
|
+
Use `StringBuffer` for building strings in loops to avoid O(n²) performance.
|
|
11
|
+
|
|
12
|
+
**Incorrect (quadratic time complexity):**
|
|
13
|
+
|
|
14
|
+
```dart
|
|
15
|
+
String buildHtml(List<Item> items) {
|
|
16
|
+
var html = '<ul>';
|
|
17
|
+
for (final item in items) {
|
|
18
|
+
html += '<li>${item.name}</li>'; // Creates new string each iteration!
|
|
19
|
+
}
|
|
20
|
+
html += '</ul>';
|
|
21
|
+
return html;
|
|
22
|
+
}
|
|
23
|
+
// For 1000 items: ~500,000 character copies
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Correct (linear time complexity):**
|
|
27
|
+
|
|
28
|
+
```dart
|
|
29
|
+
String buildHtml(List<Item> items) {
|
|
30
|
+
final buffer = StringBuffer('<ul>');
|
|
31
|
+
for (final item in items) {
|
|
32
|
+
buffer.write('<li>${item.name}</li>'); // Efficient append
|
|
33
|
+
}
|
|
34
|
+
buffer.write('</ul>');
|
|
35
|
+
return buffer.toString();
|
|
36
|
+
}
|
|
37
|
+
// For 1000 items: ~10,000 character copies
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Common patterns:**
|
|
41
|
+
|
|
42
|
+
```dart
|
|
43
|
+
// Building log messages
|
|
44
|
+
String formatLog(List<LogEntry> entries) {
|
|
45
|
+
final buffer = StringBuffer();
|
|
46
|
+
for (final entry in entries) {
|
|
47
|
+
buffer
|
|
48
|
+
..write('[${entry.timestamp}] ')
|
|
49
|
+
..write(entry.level.name.toUpperCase())
|
|
50
|
+
..write(': ')
|
|
51
|
+
..writeln(entry.message);
|
|
52
|
+
}
|
|
53
|
+
return buffer.toString();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// With writeAll for simple cases
|
|
57
|
+
String joinItems(List<String> items) {
|
|
58
|
+
final buffer = StringBuffer();
|
|
59
|
+
buffer.writeAll(items, ', '); // Adds separator
|
|
60
|
+
return buffer.toString();
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**When += is fine:**
|
|
65
|
+
- Fixed number of concatenations (not in loop)
|
|
66
|
+
- Small strings
|
|
67
|
+
- Readability matters more than performance
|
|
68
|
+
|
|
69
|
+
```dart
|
|
70
|
+
// This is fine - constant number of operations
|
|
71
|
+
final message = 'Hello, ' + name + '! Welcome to ' + appName + '.';
|
|
72
|
+
|
|
73
|
+
// Or use interpolation (preferred)
|
|
74
|
+
final message = 'Hello, $name! Welcome to $appName.';
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Rule of thumb: Use StringBuffer when concatenating in loops or building strings from many parts.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use AnimatedOpacity Instead of Opacity
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Smoother animations, less jank
|
|
5
|
+
tags: layout, opacity, animation, performance
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use AnimatedOpacity Instead of Opacity
|
|
9
|
+
|
|
10
|
+
Use `AnimatedOpacity` or `FadeTransition` instead of `Opacity` widget for animated opacity changes.
|
|
11
|
+
|
|
12
|
+
**Incorrect (causes unnecessary repaints):**
|
|
13
|
+
|
|
14
|
+
```dart
|
|
15
|
+
class BadFade extends StatefulWidget {
|
|
16
|
+
@override
|
|
17
|
+
State<BadFade> createState() => _BadFadeState();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
class _BadFadeState extends State<BadFade> {
|
|
21
|
+
double _opacity = 1.0;
|
|
22
|
+
|
|
23
|
+
@override
|
|
24
|
+
Widget build(BuildContext context) {
|
|
25
|
+
return Opacity(
|
|
26
|
+
opacity: _opacity,
|
|
27
|
+
child: ExpensiveWidget(),
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
void fadeOut() {
|
|
32
|
+
// Each setState causes full rebuild
|
|
33
|
+
for (var i = 10; i >= 0; i--) {
|
|
34
|
+
Future.delayed(Duration(milliseconds: i * 50), () {
|
|
35
|
+
setState(() => _opacity = i / 10);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Correct (AnimatedOpacity handles transitions):**
|
|
43
|
+
|
|
44
|
+
```dart
|
|
45
|
+
class GoodFade extends StatefulWidget {
|
|
46
|
+
const GoodFade({super.key});
|
|
47
|
+
|
|
48
|
+
@override
|
|
49
|
+
State<GoodFade> createState() => _GoodFadeState();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
class _GoodFadeState extends State<GoodFade> {
|
|
53
|
+
bool _visible = true;
|
|
54
|
+
|
|
55
|
+
@override
|
|
56
|
+
Widget build(BuildContext context) {
|
|
57
|
+
return AnimatedOpacity(
|
|
58
|
+
opacity: _visible ? 1.0 : 0.0,
|
|
59
|
+
duration: const Duration(milliseconds: 300),
|
|
60
|
+
child: const ExpensiveWidget(), // Only built once
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
void toggleVisibility() {
|
|
65
|
+
setState(() => _visible = !_visible);
|
|
66
|
+
// Animation handled internally without extra rebuilds
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**With FadeTransition for controller-based animations:**
|
|
72
|
+
|
|
73
|
+
```dart
|
|
74
|
+
class FadeWidget extends StatefulWidget {
|
|
75
|
+
const FadeWidget({super.key});
|
|
76
|
+
|
|
77
|
+
@override
|
|
78
|
+
State<FadeWidget> createState() => _FadeWidgetState();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
class _FadeWidgetState extends State<FadeWidget>
|
|
82
|
+
with SingleTickerProviderStateMixin {
|
|
83
|
+
late final AnimationController _controller;
|
|
84
|
+
late final Animation<double> _opacity;
|
|
85
|
+
|
|
86
|
+
@override
|
|
87
|
+
void initState() {
|
|
88
|
+
super.initState();
|
|
89
|
+
_controller = AnimationController(
|
|
90
|
+
duration: const Duration(milliseconds: 300),
|
|
91
|
+
vsync: this,
|
|
92
|
+
);
|
|
93
|
+
_opacity = CurvedAnimation(parent: _controller, curve: Curves.easeIn);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@override
|
|
97
|
+
void dispose() {
|
|
98
|
+
_controller.dispose();
|
|
99
|
+
super.dispose();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@override
|
|
103
|
+
Widget build(BuildContext context) {
|
|
104
|
+
return FadeTransition(
|
|
105
|
+
opacity: _opacity,
|
|
106
|
+
child: const ExpensiveWidget(),
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Set clipBehavior.none When Not Needed
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Reduces GPU overdraw
|
|
5
|
+
tags: layout, clip, performance, rendering
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Set clipBehavior.none When Not Needed
|
|
9
|
+
|
|
10
|
+
Disable clipping on containers and stacks when content doesn't overflow.
|
|
11
|
+
|
|
12
|
+
**Incorrect (unnecessary clipping):**
|
|
13
|
+
|
|
14
|
+
```dart
|
|
15
|
+
class BadLayout extends StatelessWidget {
|
|
16
|
+
@override
|
|
17
|
+
Widget build(BuildContext context) {
|
|
18
|
+
return Container(
|
|
19
|
+
// Default clipBehavior may clip unnecessarily
|
|
20
|
+
decoration: BoxDecoration(
|
|
21
|
+
color: Colors.white,
|
|
22
|
+
borderRadius: BorderRadius.circular(8),
|
|
23
|
+
),
|
|
24
|
+
child: Row(
|
|
25
|
+
children: [
|
|
26
|
+
Icon(Icons.star),
|
|
27
|
+
Text('Rating'),
|
|
28
|
+
],
|
|
29
|
+
),
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Correct (explicit no clipping):**
|
|
36
|
+
|
|
37
|
+
```dart
|
|
38
|
+
class GoodLayout extends StatelessWidget {
|
|
39
|
+
const GoodLayout({super.key});
|
|
40
|
+
|
|
41
|
+
@override
|
|
42
|
+
Widget build(BuildContext context) {
|
|
43
|
+
return Container(
|
|
44
|
+
clipBehavior: Clip.none, // Explicit - no clipping needed
|
|
45
|
+
decoration: BoxDecoration(
|
|
46
|
+
color: Colors.white,
|
|
47
|
+
borderRadius: BorderRadius.circular(8),
|
|
48
|
+
),
|
|
49
|
+
child: const Row(
|
|
50
|
+
children: [
|
|
51
|
+
Icon(Icons.star),
|
|
52
|
+
Text('Rating'),
|
|
53
|
+
],
|
|
54
|
+
),
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Stack with overflow:**
|
|
61
|
+
|
|
62
|
+
```dart
|
|
63
|
+
// When children intentionally overflow
|
|
64
|
+
Stack(
|
|
65
|
+
clipBehavior: Clip.none, // Allow overflow
|
|
66
|
+
children: [
|
|
67
|
+
Container(width: 100, height: 100, color: Colors.blue),
|
|
68
|
+
Positioned(
|
|
69
|
+
top: -20, // Overflows parent - needs Clip.none
|
|
70
|
+
child: Icon(Icons.star),
|
|
71
|
+
),
|
|
72
|
+
],
|
|
73
|
+
)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Clip options:**
|
|
77
|
+
|
|
78
|
+
```dart
|
|
79
|
+
// Clip.none - No clipping, best performance
|
|
80
|
+
// Clip.hardEdge - Fastest clipping if needed
|
|
81
|
+
// Clip.antiAlias - Smooth edges, slower
|
|
82
|
+
// Clip.antiAliasWithSaveLayer - Smoothest, slowest
|
|
83
|
+
|
|
84
|
+
// For rounded containers that need clipping:
|
|
85
|
+
Container(
|
|
86
|
+
clipBehavior: Clip.hardEdge, // Use simplest clip that works
|
|
87
|
+
decoration: BoxDecoration(
|
|
88
|
+
borderRadius: BorderRadius.circular(8),
|
|
89
|
+
),
|
|
90
|
+
child: Image.network(url), // Image must be clipped
|
|
91
|
+
)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Use `Clip.none` as default, only enable clipping when content overflows.
|