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,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Cache Animation Frames When Possible
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Reduces per-frame computation
|
|
5
|
+
tags: animation, cache, frames, performance
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Cache Animation Frames When Possible
|
|
9
|
+
|
|
10
|
+
For complex animations that repeat, consider caching rendered frames or using simpler representations during motion.
|
|
11
|
+
|
|
12
|
+
**Incorrect (complex rendering every frame):**
|
|
13
|
+
|
|
14
|
+
```dart
|
|
15
|
+
class BadComplexAnim extends StatefulWidget {
|
|
16
|
+
@override
|
|
17
|
+
State<BadComplexAnim> createState() => _BadComplexAnimState();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
class _BadComplexAnimState extends State<BadComplexAnim>
|
|
21
|
+
with SingleTickerProviderStateMixin {
|
|
22
|
+
late AnimationController _controller;
|
|
23
|
+
|
|
24
|
+
@override
|
|
25
|
+
Widget build(BuildContext context) {
|
|
26
|
+
return AnimatedBuilder(
|
|
27
|
+
animation: _controller,
|
|
28
|
+
builder: (context, _) {
|
|
29
|
+
// Complex widget rebuilt 60 times per second
|
|
30
|
+
return Transform.rotate(
|
|
31
|
+
angle: _controller.value * 2 * pi,
|
|
32
|
+
child: ComplexShadowedWidget(), // Expensive!
|
|
33
|
+
);
|
|
34
|
+
},
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Correct (cache animated content in layer):**
|
|
41
|
+
|
|
42
|
+
```dart
|
|
43
|
+
class GoodComplexAnim extends StatefulWidget {
|
|
44
|
+
const GoodComplexAnim({super.key});
|
|
45
|
+
|
|
46
|
+
@override
|
|
47
|
+
State<GoodComplexAnim> createState() => _GoodComplexAnimState();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
class _GoodComplexAnimState extends State<GoodComplexAnim>
|
|
51
|
+
with SingleTickerProviderStateMixin {
|
|
52
|
+
late AnimationController _controller;
|
|
53
|
+
|
|
54
|
+
@override
|
|
55
|
+
Widget build(BuildContext context) {
|
|
56
|
+
return AnimatedBuilder(
|
|
57
|
+
animation: _controller,
|
|
58
|
+
// Child is built ONCE and cached
|
|
59
|
+
child: const RepaintBoundary(
|
|
60
|
+
child: ComplexShadowedWidget(),
|
|
61
|
+
),
|
|
62
|
+
builder: (context, child) {
|
|
63
|
+
// Only transform is applied per-frame
|
|
64
|
+
return Transform.rotate(
|
|
65
|
+
angle: _controller.value * 2 * pi,
|
|
66
|
+
child: child, // Reused cached child
|
|
67
|
+
);
|
|
68
|
+
},
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**For repeated sprite animations:**
|
|
75
|
+
|
|
76
|
+
```dart
|
|
77
|
+
class SpriteAnimation extends StatelessWidget {
|
|
78
|
+
final List<ui.Image> frames; // Pre-rendered frames
|
|
79
|
+
final int currentFrame;
|
|
80
|
+
|
|
81
|
+
const SpriteAnimation({
|
|
82
|
+
super.key,
|
|
83
|
+
required this.frames,
|
|
84
|
+
required this.currentFrame,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
@override
|
|
88
|
+
Widget build(BuildContext context) {
|
|
89
|
+
return CustomPaint(
|
|
90
|
+
painter: SpritePainter(frames[currentFrame]),
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
class SpritePainter extends CustomPainter {
|
|
96
|
+
final ui.Image frame;
|
|
97
|
+
SpritePainter(this.frame);
|
|
98
|
+
|
|
99
|
+
@override
|
|
100
|
+
void paint(Canvas canvas, Size size) {
|
|
101
|
+
canvas.drawImage(frame, Offset.zero, Paint());
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
@override
|
|
105
|
+
bool shouldRepaint(SpritePainter old) => frame != old.frame;
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**When to cache:**
|
|
110
|
+
- Rotating complex widgets with shadows
|
|
111
|
+
- Scaling elaborate UI
|
|
112
|
+
- Looping animations with static content
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use Physics-Based Animations
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: More natural, interruptible animations
|
|
5
|
+
tags: animation, physics, spring, fling
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use Physics-Based Animations
|
|
9
|
+
|
|
10
|
+
Use physics-based animations for natural feeling interactions that can be interrupted and redirected.
|
|
11
|
+
|
|
12
|
+
**Incorrect (fixed duration feels artificial):**
|
|
13
|
+
|
|
14
|
+
```dart
|
|
15
|
+
class BadDrag extends StatefulWidget {
|
|
16
|
+
@override
|
|
17
|
+
State<BadDrag> createState() => _BadDragState();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
class _BadDragState extends State<BadDrag>
|
|
21
|
+
with SingleTickerProviderStateMixin {
|
|
22
|
+
late AnimationController _controller;
|
|
23
|
+
double _offset = 0;
|
|
24
|
+
|
|
25
|
+
void _onDragEnd(DragEndDetails details) {
|
|
26
|
+
// Fixed duration regardless of velocity - unnatural
|
|
27
|
+
_controller.animateTo(
|
|
28
|
+
0,
|
|
29
|
+
duration: const Duration(milliseconds: 300),
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Correct (physics-based spring):**
|
|
36
|
+
|
|
37
|
+
```dart
|
|
38
|
+
class GoodDrag extends StatefulWidget {
|
|
39
|
+
const GoodDrag({super.key});
|
|
40
|
+
|
|
41
|
+
@override
|
|
42
|
+
State<GoodDrag> createState() => _GoodDragState();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
class _GoodDragState extends State<GoodDrag>
|
|
46
|
+
with SingleTickerProviderStateMixin {
|
|
47
|
+
late AnimationController _controller;
|
|
48
|
+
|
|
49
|
+
void _onDragEnd(DragEndDetails details) {
|
|
50
|
+
// Spring simulation - natural bounce back
|
|
51
|
+
final simulation = SpringSimulation(
|
|
52
|
+
SpringDescription(
|
|
53
|
+
mass: 1,
|
|
54
|
+
stiffness: 500,
|
|
55
|
+
damping: 25,
|
|
56
|
+
),
|
|
57
|
+
_controller.value, // Current position
|
|
58
|
+
0, // Target position
|
|
59
|
+
details.velocity.pixelsPerSecond.dx / 1000, // Initial velocity
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
_controller.animateWith(simulation);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Fling animation for scrolling:**
|
|
68
|
+
|
|
69
|
+
```dart
|
|
70
|
+
void _onPanEnd(DragEndDetails details) {
|
|
71
|
+
final velocity = details.velocity.pixelsPerSecond.dx;
|
|
72
|
+
|
|
73
|
+
final simulation = FrictionSimulation(
|
|
74
|
+
0.135, // Friction coefficient
|
|
75
|
+
_position,
|
|
76
|
+
velocity,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
_controller.animateWith(simulation);
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Built-in spring curves:**
|
|
84
|
+
|
|
85
|
+
```dart
|
|
86
|
+
// Use Curves.elasticOut for spring-like effect
|
|
87
|
+
AnimatedContainer(
|
|
88
|
+
duration: const Duration(milliseconds: 500),
|
|
89
|
+
curve: Curves.elasticOut, // Spring overshoot
|
|
90
|
+
transform: Matrix4.translationValues(_offset, 0, 0),
|
|
91
|
+
child: content,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
// Or SpringCurve for more control
|
|
95
|
+
const spring = SpringDescription(
|
|
96
|
+
mass: 1,
|
|
97
|
+
stiffness: 100,
|
|
98
|
+
damping: 10,
|
|
99
|
+
);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Physics animations feel more natural because:
|
|
103
|
+
- Duration depends on distance and velocity
|
|
104
|
+
- Can be interrupted mid-animation
|
|
105
|
+
- Respect user gesture momentum
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Minimize Overdraw in Animations
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Reduces GPU workload
|
|
5
|
+
tags: animation, overdraw, gpu, performance
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Minimize Overdraw in Animations
|
|
9
|
+
|
|
10
|
+
Reduce overlapping painted areas during animations to decrease GPU workload.
|
|
11
|
+
|
|
12
|
+
**Incorrect (multiple overlapping layers):**
|
|
13
|
+
|
|
14
|
+
```dart
|
|
15
|
+
class BadOverdraw extends StatelessWidget {
|
|
16
|
+
@override
|
|
17
|
+
Widget build(BuildContext context) {
|
|
18
|
+
return Stack(
|
|
19
|
+
children: [
|
|
20
|
+
// Background painted everywhere
|
|
21
|
+
Container(color: Colors.blue),
|
|
22
|
+
// Card overlaps background
|
|
23
|
+
Center(
|
|
24
|
+
child: Container(
|
|
25
|
+
width: 200,
|
|
26
|
+
height: 200,
|
|
27
|
+
decoration: BoxDecoration(
|
|
28
|
+
color: Colors.white,
|
|
29
|
+
boxShadow: [
|
|
30
|
+
BoxShadow(
|
|
31
|
+
blurRadius: 20,
|
|
32
|
+
color: Colors.black26,
|
|
33
|
+
),
|
|
34
|
+
],
|
|
35
|
+
),
|
|
36
|
+
// Icon overlaps card
|
|
37
|
+
child: Icon(Icons.star, size: 100, color: Colors.yellow),
|
|
38
|
+
),
|
|
39
|
+
),
|
|
40
|
+
],
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Correct (minimize overlap):**
|
|
47
|
+
|
|
48
|
+
```dart
|
|
49
|
+
class GoodOverdraw extends StatelessWidget {
|
|
50
|
+
const GoodOverdraw({super.key});
|
|
51
|
+
|
|
52
|
+
@override
|
|
53
|
+
Widget build(BuildContext context) {
|
|
54
|
+
return ColoredBox(
|
|
55
|
+
color: Colors.blue,
|
|
56
|
+
child: Center(
|
|
57
|
+
child: PhysicalModel(
|
|
58
|
+
color: Colors.white,
|
|
59
|
+
elevation: 8, // Uses GPU shadow, more efficient
|
|
60
|
+
borderRadius: BorderRadius.circular(8),
|
|
61
|
+
child: const SizedBox(
|
|
62
|
+
width: 200,
|
|
63
|
+
height: 200,
|
|
64
|
+
child: Icon(Icons.star, size: 100, color: Colors.yellow),
|
|
65
|
+
),
|
|
66
|
+
),
|
|
67
|
+
),
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Debug overdraw:**
|
|
74
|
+
|
|
75
|
+
```dart
|
|
76
|
+
void main() {
|
|
77
|
+
// Shows overdraw with colors
|
|
78
|
+
// Green = 1x, Light green = 2x, Yellow = 3x, Red = 4x+
|
|
79
|
+
debugPaintLayerBordersEnabled = true;
|
|
80
|
+
runApp(MyApp());
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Or use DevTools Performance Overlay
|
|
84
|
+
MaterialApp(
|
|
85
|
+
showPerformanceOverlay: true,
|
|
86
|
+
home: MyHomePage(),
|
|
87
|
+
)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Tips to reduce overdraw:**
|
|
91
|
+
- Use `ColoredBox` instead of `Container(color: ...)`
|
|
92
|
+
- Use `PhysicalModel` for shadows instead of `BoxShadow`
|
|
93
|
+
- Avoid stacking opaque widgets
|
|
94
|
+
- Use `saveLayer` sparingly (opacity, clips with antialiasing)
|
|
95
|
+
- Prefer solid backgrounds over gradients where possible
|
|
96
|
+
|
|
97
|
+
**For animated opacity:**
|
|
98
|
+
|
|
99
|
+
```dart
|
|
100
|
+
// Bad - saveLayer on every frame
|
|
101
|
+
Opacity(
|
|
102
|
+
opacity: animation.value,
|
|
103
|
+
child: ComplexWidget(),
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
// Better - use FadeTransition
|
|
107
|
+
FadeTransition(
|
|
108
|
+
opacity: animation,
|
|
109
|
+
child: const ComplexWidget(),
|
|
110
|
+
)
|
|
111
|
+
```
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use TweenSequence for Complex Animations
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Cleaner multi-phase animations
|
|
5
|
+
tags: animation, tween, sequence, multi-step
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use TweenSequence for Complex Animations
|
|
9
|
+
|
|
10
|
+
Use `TweenSequence` for multi-phase animations instead of chaining or complex state logic.
|
|
11
|
+
|
|
12
|
+
**Incorrect (manual phase management):**
|
|
13
|
+
|
|
14
|
+
```dart
|
|
15
|
+
class BadMultiPhase extends StatefulWidget {
|
|
16
|
+
@override
|
|
17
|
+
State<BadMultiPhase> createState() => _BadMultiPhaseState();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
class _BadMultiPhaseState extends State<BadMultiPhase>
|
|
21
|
+
with SingleTickerProviderStateMixin {
|
|
22
|
+
late AnimationController _controller;
|
|
23
|
+
int _phase = 0;
|
|
24
|
+
|
|
25
|
+
@override
|
|
26
|
+
void initState() {
|
|
27
|
+
super.initState();
|
|
28
|
+
_controller = AnimationController(
|
|
29
|
+
duration: const Duration(seconds: 1),
|
|
30
|
+
vsync: this,
|
|
31
|
+
)..addStatusListener((status) {
|
|
32
|
+
if (status == AnimationStatus.completed) {
|
|
33
|
+
_phase++;
|
|
34
|
+
if (_phase < 3) _controller.forward(from: 0);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
double get _scale {
|
|
40
|
+
switch (_phase) {
|
|
41
|
+
case 0: return 1.0 + _controller.value * 0.5; // 1.0 -> 1.5
|
|
42
|
+
case 1: return 1.5 - _controller.value * 0.3; // 1.5 -> 1.2
|
|
43
|
+
case 2: return 1.2 - _controller.value * 0.2; // 1.2 -> 1.0
|
|
44
|
+
default: return 1.0;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Correct (TweenSequence):**
|
|
51
|
+
|
|
52
|
+
```dart
|
|
53
|
+
class GoodMultiPhase extends StatefulWidget {
|
|
54
|
+
const GoodMultiPhase({super.key});
|
|
55
|
+
|
|
56
|
+
@override
|
|
57
|
+
State<GoodMultiPhase> createState() => _GoodMultiPhaseState();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
class _GoodMultiPhaseState extends State<GoodMultiPhase>
|
|
61
|
+
with SingleTickerProviderStateMixin {
|
|
62
|
+
late AnimationController _controller;
|
|
63
|
+
late Animation<double> _scaleAnimation;
|
|
64
|
+
|
|
65
|
+
@override
|
|
66
|
+
void initState() {
|
|
67
|
+
super.initState();
|
|
68
|
+
_controller = AnimationController(
|
|
69
|
+
duration: const Duration(seconds: 3),
|
|
70
|
+
vsync: this,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
_scaleAnimation = TweenSequence<double>([
|
|
74
|
+
TweenSequenceItem(
|
|
75
|
+
tween: Tween(begin: 1.0, end: 1.5)
|
|
76
|
+
.chain(CurveTween(curve: Curves.easeOut)),
|
|
77
|
+
weight: 33,
|
|
78
|
+
),
|
|
79
|
+
TweenSequenceItem(
|
|
80
|
+
tween: Tween(begin: 1.5, end: 1.2)
|
|
81
|
+
.chain(CurveTween(curve: Curves.easeInOut)),
|
|
82
|
+
weight: 33,
|
|
83
|
+
),
|
|
84
|
+
TweenSequenceItem(
|
|
85
|
+
tween: Tween(begin: 1.2, end: 1.0)
|
|
86
|
+
.chain(CurveTween(curve: Curves.easeIn)),
|
|
87
|
+
weight: 34,
|
|
88
|
+
),
|
|
89
|
+
]).animate(_controller);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@override
|
|
93
|
+
void dispose() {
|
|
94
|
+
_controller.dispose();
|
|
95
|
+
super.dispose();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@override
|
|
99
|
+
Widget build(BuildContext context) {
|
|
100
|
+
return ScaleTransition(
|
|
101
|
+
scale: _scaleAnimation,
|
|
102
|
+
child: const Icon(Icons.favorite, size: 50),
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Benefits:**
|
|
109
|
+
- Single animation controller
|
|
110
|
+
- Declarative phase definitions
|
|
111
|
+
- Each phase can have its own curve
|
|
112
|
+
- Weight controls relative duration
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Cancel Stream Subscriptions on Dispose
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Prevents memory leaks and errors
|
|
5
|
+
tags: async, streams, dispose, memory
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Cancel Stream Subscriptions on Dispose
|
|
9
|
+
|
|
10
|
+
Always cancel stream subscriptions in `dispose()` to prevent memory leaks and errors from updates to disposed widgets.
|
|
11
|
+
|
|
12
|
+
**Incorrect (subscription never cancelled):**
|
|
13
|
+
|
|
14
|
+
```dart
|
|
15
|
+
class BadStreamWidget extends StatefulWidget {
|
|
16
|
+
@override
|
|
17
|
+
State<BadStreamWidget> createState() => _BadStreamWidgetState();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
class _BadStreamWidgetState extends State<BadStreamWidget> {
|
|
21
|
+
String data = '';
|
|
22
|
+
|
|
23
|
+
@override
|
|
24
|
+
void initState() {
|
|
25
|
+
super.initState();
|
|
26
|
+
// This subscription will leak!
|
|
27
|
+
myStream.listen((value) {
|
|
28
|
+
setState(() => data = value);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@override
|
|
33
|
+
Widget build(BuildContext context) => Text(data);
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Correct (subscription cancelled on dispose):**
|
|
38
|
+
|
|
39
|
+
```dart
|
|
40
|
+
class GoodStreamWidget extends StatefulWidget {
|
|
41
|
+
const GoodStreamWidget({super.key});
|
|
42
|
+
|
|
43
|
+
@override
|
|
44
|
+
State<GoodStreamWidget> createState() => _GoodStreamWidgetState();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
class _GoodStreamWidgetState extends State<GoodStreamWidget> {
|
|
48
|
+
String data = '';
|
|
49
|
+
StreamSubscription<String>? _subscription;
|
|
50
|
+
|
|
51
|
+
@override
|
|
52
|
+
void initState() {
|
|
53
|
+
super.initState();
|
|
54
|
+
_subscription = myStream.listen((value) {
|
|
55
|
+
setState(() => data = value);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@override
|
|
60
|
+
void dispose() {
|
|
61
|
+
_subscription?.cancel(); // Critical!
|
|
62
|
+
super.dispose();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@override
|
|
66
|
+
Widget build(BuildContext context) => Text(data);
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Or use StreamBuilder (handles subscription automatically):**
|
|
71
|
+
|
|
72
|
+
```dart
|
|
73
|
+
class BetterStreamWidget extends StatelessWidget {
|
|
74
|
+
const BetterStreamWidget({super.key});
|
|
75
|
+
|
|
76
|
+
@override
|
|
77
|
+
Widget build(BuildContext context) {
|
|
78
|
+
return StreamBuilder<String>(
|
|
79
|
+
stream: myStream,
|
|
80
|
+
builder: (context, snapshot) {
|
|
81
|
+
if (snapshot.hasData) {
|
|
82
|
+
return Text(snapshot.data!);
|
|
83
|
+
}
|
|
84
|
+
return const CircularProgressIndicator();
|
|
85
|
+
},
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Multiple subscriptions pattern:**
|
|
92
|
+
|
|
93
|
+
```dart
|
|
94
|
+
class _MultiStreamState extends State<MultiStreamWidget> {
|
|
95
|
+
final List<StreamSubscription> _subscriptions = [];
|
|
96
|
+
|
|
97
|
+
@override
|
|
98
|
+
void initState() {
|
|
99
|
+
super.initState();
|
|
100
|
+
_subscriptions.add(stream1.listen((_) {}));
|
|
101
|
+
_subscriptions.add(stream2.listen((_) {}));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
@override
|
|
105
|
+
void dispose() {
|
|
106
|
+
for (final sub in _subscriptions) {
|
|
107
|
+
sub.cancel();
|
|
108
|
+
}
|
|
109
|
+
super.dispose();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use compute() for Heavy Synchronous Work
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Prevents UI jank
|
|
5
|
+
tags: async, compute, isolate, performance
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use compute() for Heavy Synchronous Work
|
|
9
|
+
|
|
10
|
+
Offload CPU-intensive synchronous work to an isolate using `compute()` to prevent UI jank.
|
|
11
|
+
|
|
12
|
+
**Incorrect (blocks UI thread):**
|
|
13
|
+
|
|
14
|
+
```dart
|
|
15
|
+
class BadJsonParser extends StatelessWidget {
|
|
16
|
+
Future<void> parseData() async {
|
|
17
|
+
final response = await http.get(Uri.parse('api/data'));
|
|
18
|
+
|
|
19
|
+
// This blocks the UI thread!
|
|
20
|
+
final parsed = jsonDecode(response.body);
|
|
21
|
+
final processed = heavyProcessing(parsed);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Correct (offload to isolate):**
|
|
27
|
+
|
|
28
|
+
```dart
|
|
29
|
+
class GoodJsonParser extends StatelessWidget {
|
|
30
|
+
const GoodJsonParser({super.key});
|
|
31
|
+
|
|
32
|
+
// Must be top-level or static function
|
|
33
|
+
static Map<String, dynamic> parseJson(String json) {
|
|
34
|
+
final parsed = jsonDecode(json);
|
|
35
|
+
return heavyProcessing(parsed);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
Future<Map<String, dynamic>> parseData() async {
|
|
39
|
+
final response = await http.get(Uri.parse('api/data'));
|
|
40
|
+
|
|
41
|
+
// Runs in separate isolate, UI stays responsive
|
|
42
|
+
return compute(parseJson, response.body);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**For multiple heavy operations:**
|
|
48
|
+
|
|
49
|
+
```dart
|
|
50
|
+
Future<ProcessedData> processAll(RawData raw) async {
|
|
51
|
+
// Run heavy computations in parallel on separate isolates
|
|
52
|
+
final results = await Future.wait([
|
|
53
|
+
compute(parseImages, raw.images),
|
|
54
|
+
compute(parseVideos, raw.videos),
|
|
55
|
+
compute(parseDocuments, raw.documents),
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
return ProcessedData(
|
|
59
|
+
images: results[0],
|
|
60
|
+
videos: results[1],
|
|
61
|
+
documents: results[2],
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**When to use compute():**
|
|
67
|
+
- JSON parsing of large payloads (>100KB)
|
|
68
|
+
- Image processing or compression
|
|
69
|
+
- Cryptographic operations
|
|
70
|
+
- Complex data transformations
|
|
71
|
+
- Any operation taking >16ms
|
|
72
|
+
|
|
73
|
+
**Limitations:**
|
|
74
|
+
- Function must be top-level or static
|
|
75
|
+
- Arguments must be serializable (no closures)
|
|
76
|
+
- Has isolate spawn overhead (~5-20ms)
|
|
77
|
+
|
|
78
|
+
For very frequent operations, consider using `Isolate.spawn` with ports for persistent workers.
|