ginskill-init 2.7.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/.wrangler/cache/pages.json +4 -0
- package/.wrangler/cache/wrangler-account.json +6 -0
- package/DEVELOPMENT.md +510 -0
- package/README.md +104 -0
- package/agents/developer.md +56 -0
- package/agents/frontend-design.md +69 -0
- package/agents/mobile-reviewer.md +36 -0
- package/agents/review-code.md +49 -0
- package/agents/security-scanner.md +50 -0
- package/agents/tester.md +72 -0
- package/bin/cli.js +461 -0
- package/landing/ai-build-ai.png +0 -0
- package/landing/index.html +1495 -0
- package/landing/logo.png +0 -0
- package/package.json +37 -0
- package/skills/active-life-dev/SKILL.md +157 -0
- package/skills/active-life-dev/docs/auth.md +187 -0
- package/skills/active-life-dev/docs/customers.md +216 -0
- package/skills/active-life-dev/docs/integrations.md +209 -0
- package/skills/active-life-dev/docs/inventory.md +192 -0
- package/skills/active-life-dev/docs/modules.md +181 -0
- package/skills/active-life-dev/docs/orders.md +180 -0
- package/skills/active-life-dev/docs/patterns.md +319 -0
- package/skills/active-life-dev/docs/products.md +216 -0
- package/skills/active-life-dev/docs/schema.md +502 -0
- package/skills/active-life-dev/docs/setup.md +169 -0
- package/skills/active-life-dev/docs/vouchers.md +144 -0
- package/skills/ai-asset-generator/SKILL.md +247 -0
- package/skills/ai-asset-generator/docs/gen-image.md +274 -0
- package/skills/ai-asset-generator/docs/genvideo.md +341 -0
- package/skills/ai-asset-generator/docs/remove-background.md +19 -0
- package/skills/ai-asset-generator/lib/bg-remove.mjs +34 -0
- package/skills/ai-asset-generator/lib/env.mjs +48 -0
- package/skills/ai-asset-generator/lib/kie-client.mjs +100 -0
- package/skills/ai-build-ai/SKILL.md +127 -0
- package/skills/ai-build-ai/docs/agent-teams.md +293 -0
- package/skills/ai-build-ai/docs/checkpointing.md +161 -0
- package/skills/ai-build-ai/docs/create-agent.md +399 -0
- package/skills/ai-build-ai/docs/create-mcp.md +395 -0
- package/skills/ai-build-ai/docs/create-skill.md +299 -0
- package/skills/ai-build-ai/docs/headless-mode.md +614 -0
- package/skills/ai-build-ai/docs/hooks.md +578 -0
- package/skills/ai-build-ai/docs/memory-claude-md.md +375 -0
- package/skills/ai-build-ai/docs/output-styles.md +208 -0
- package/skills/ai-build-ai/docs/overview.md +162 -0
- package/skills/ai-build-ai/docs/permissions.md +391 -0
- package/skills/ai-build-ai/docs/plugins.md +396 -0
- package/skills/ai-build-ai/docs/sandbox.md +262 -0
- package/skills/ai-build-ai/docs/team-lead-workflow.md +648 -0
- package/skills/ant-design/SKILL.md +323 -0
- package/skills/ant-design/docs/components.md +160 -0
- package/skills/ant-design/docs/data-entry.md +406 -0
- package/skills/ant-design/docs/display.md +594 -0
- package/skills/ant-design/docs/feedback.md +451 -0
- package/skills/ant-design/docs/key-components.md +414 -0
- package/skills/ant-design/docs/navigation.md +310 -0
- package/skills/ant-design/docs/pro-components.md +543 -0
- package/skills/ant-design/docs/setup.md +213 -0
- package/skills/ant-design/docs/theme.md +265 -0
- package/skills/flutter-performance/SKILL.md +803 -0
- package/skills/flutter-performance/references/flutter-patterns.md +595 -0
- package/skills/icon-generator/SKILL.md +270 -0
- package/skills/mobile-app-review/SKILL.md +321 -0
- package/skills/mobile-app-review/references/apple-review.md +132 -0
- package/skills/mobile-app-review/references/google-play-review.md +203 -0
- package/skills/mongodb/SKILL.md +667 -0
- package/skills/mongodb/references/mongoose-patterns.md +368 -0
- package/skills/nestjs-architecture/SKILL.md +1086 -0
- package/skills/nestjs-architecture/references/advanced-patterns.md +590 -0
- package/skills/performance/SKILL.md +509 -0
- package/skills/react-fsd-architecture/SKILL.md +693 -0
- package/skills/react-fsd-architecture/references/fsd-patterns.md +747 -0
- package/skills/react-native-expo/SKILL.md +128 -0
- package/skills/react-native-expo/references/data-layer.md +252 -0
- package/skills/react-native-expo/references/design-system.md +252 -0
- package/skills/react-native-expo/references/navigation.md +199 -0
- package/skills/react-native-expo/references/performance.md +229 -0
- package/skills/react-native-expo/references/platform-services.md +179 -0
- package/skills/react-native-expo/references/state-management.md +209 -0
- package/skills/react-native-expo/references/ui-patterns.md +301 -0
- package/skills/react-query/SKILL.md +685 -0
- package/skills/react-query/references/query-patterns.md +365 -0
- package/skills/review-code/SKILL.md +374 -0
- package/skills/review-code/references/clean-code-principles.md +395 -0
- package/skills/review-code/references/frontend-patterns.md +136 -0
- package/skills/review-code/references/nestjs-patterns.md +184 -0
- package/skills/security-scanner/SKILL.md +366 -0
- package/skills/security-scanner/references/nestjs-security.md +260 -0
- package/skills/security-scanner/references/nextjs-security.md +201 -0
- package/skills/security-scanner/references/react-native-security.md +199 -0
- package/skills/traefik/SKILL.md +105 -0
- package/skills/traefik/docs/advanced-routing.md +186 -0
- package/skills/traefik/docs/auth-providers.md +137 -0
- package/skills/traefik/docs/cicd-devops.md +396 -0
- package/skills/traefik/docs/core-config.md +171 -0
- package/skills/traefik/docs/distributed-config.md +96 -0
- package/skills/traefik/docs/docker-compose.md +182 -0
- package/skills/traefik/docs/ha-performance.md +177 -0
- package/skills/traefik/docs/kubernetes.md +278 -0
- package/skills/traefik/docs/middleware.md +205 -0
- package/skills/traefik/docs/monitoring.md +357 -0
- package/skills/traefik/docs/security.md +391 -0
- package/skills/traefik/docs/tls-acme.md +155 -0
- package/skills/ui-ux-pro-max/SKILL.md +377 -0
- package/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/skills/ui-ux-pro-max/data/styles.csv +68 -0
- package/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
# Flutter Performance — Advanced Patterns Reference
|
|
2
|
+
|
|
3
|
+
Deep-dive reference for advanced Flutter performance optimization. Read this when the SKILL.md quick reference isn't enough.
|
|
4
|
+
|
|
5
|
+
## 1. Impeller Internals & Optimization
|
|
6
|
+
|
|
7
|
+
### How Impeller Renders
|
|
8
|
+
|
|
9
|
+
Impeller replaces Skia as Flutter's rendering backend:
|
|
10
|
+
1. **Build time**: All shaders compiled offline during Flutter Engine build
|
|
11
|
+
2. **Tile-based rendering**: Divides each frame into ~256x256 pixel tiles
|
|
12
|
+
3. **Dirty region tracking**: Only re-rasterizes tiles whose content changed
|
|
13
|
+
4. **Pipeline state objects**: Built upfront, not lazily at runtime
|
|
14
|
+
|
|
15
|
+
### Impeller vs Skia Performance Comparison
|
|
16
|
+
|
|
17
|
+
| Metric | Skia | Impeller |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| First-frame shader compile | 50-300ms jank | 0ms (pre-compiled) |
|
|
20
|
+
| Frame rasterization | Baseline | ~50% faster |
|
|
21
|
+
| 120fps consistency | Frequent drops | Stable |
|
|
22
|
+
| GPU memory usage | Higher (shader cache) | Lower (no runtime cache) |
|
|
23
|
+
| Complex clip paths | Fast | Slower (more draw calls) |
|
|
24
|
+
|
|
25
|
+
### Impeller-Aware Coding Patterns
|
|
26
|
+
|
|
27
|
+
**Avoid complex clip operations:**
|
|
28
|
+
```dart
|
|
29
|
+
// Impeller generates more draw calls for complex clips
|
|
30
|
+
// BAD — custom clipper with bezier curves
|
|
31
|
+
ClipPath(
|
|
32
|
+
clipper: WavyClipper(), // complex path = many draw calls
|
|
33
|
+
child: Image.asset('bg.webp'),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
// GOOD — use decoration or simple clips
|
|
37
|
+
Container(
|
|
38
|
+
decoration: BoxDecoration(
|
|
39
|
+
borderRadius: BorderRadius.circular(16),
|
|
40
|
+
image: DecorationImage(image: AssetImage('bg.webp'), fit: BoxFit.cover),
|
|
41
|
+
),
|
|
42
|
+
)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Minimize overlapping opacity layers:**
|
|
46
|
+
```dart
|
|
47
|
+
// BAD — stacked Opacity widgets create separate composition layers
|
|
48
|
+
Stack(children: [
|
|
49
|
+
Opacity(opacity: 0.8, child: Background()),
|
|
50
|
+
Opacity(opacity: 0.5, child: Overlay()),
|
|
51
|
+
Opacity(opacity: 0.3, child: Shadow()),
|
|
52
|
+
])
|
|
53
|
+
|
|
54
|
+
// GOOD — combine into single paint operation
|
|
55
|
+
Stack(children: [
|
|
56
|
+
Background(),
|
|
57
|
+
Container(color: Colors.black.withOpacity(0.5)),
|
|
58
|
+
// Use color-level opacity, not widget-level
|
|
59
|
+
])
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Checking Impeller Status
|
|
63
|
+
|
|
64
|
+
```dart
|
|
65
|
+
// Verify Impeller is active in debug builds
|
|
66
|
+
import 'dart:ui' as ui;
|
|
67
|
+
|
|
68
|
+
void checkImpeller() {
|
|
69
|
+
// In Flutter 3.27+, Impeller is default on iOS and Android API 29+
|
|
70
|
+
debugPrint('Renderer: ${ui.PlatformDispatcher.instance.implicitView?.display}');
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Disabling Impeller (Troubleshooting Only)
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# iOS — Info.plist
|
|
78
|
+
<key>FLTEnableImpeller</key>
|
|
79
|
+
<false/>
|
|
80
|
+
|
|
81
|
+
# Android — AndroidManifest.xml
|
|
82
|
+
<meta-data
|
|
83
|
+
android:name="io.flutter.embedding.android.EnableImpeller"
|
|
84
|
+
android:value="false" />
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## 2. Custom RenderObject Performance
|
|
88
|
+
|
|
89
|
+
For maximum rendering performance, bypass the widget layer:
|
|
90
|
+
|
|
91
|
+
```dart
|
|
92
|
+
class FastCircle extends LeafRenderObjectWidget {
|
|
93
|
+
final Color color;
|
|
94
|
+
final double radius;
|
|
95
|
+
|
|
96
|
+
const FastCircle({super.key, required this.color, required this.radius});
|
|
97
|
+
|
|
98
|
+
@override
|
|
99
|
+
RenderObject createRenderObject(BuildContext context) {
|
|
100
|
+
return _RenderFastCircle(color: color, radius: radius);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@override
|
|
104
|
+
void updateRenderObject(BuildContext context, _RenderFastCircle renderObject) {
|
|
105
|
+
renderObject
|
|
106
|
+
..color = color
|
|
107
|
+
..radius = radius;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
class _RenderFastCircle extends RenderBox {
|
|
112
|
+
Color _color;
|
|
113
|
+
double _radius;
|
|
114
|
+
|
|
115
|
+
_RenderFastCircle({required Color color, required double radius})
|
|
116
|
+
: _color = color,
|
|
117
|
+
_radius = radius;
|
|
118
|
+
|
|
119
|
+
set color(Color value) {
|
|
120
|
+
if (_color == value) return; // skip if unchanged
|
|
121
|
+
_color = value;
|
|
122
|
+
markNeedsPaint(); // only repaint, no layout
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
set radius(double value) {
|
|
126
|
+
if (_radius == value) return;
|
|
127
|
+
_radius = value;
|
|
128
|
+
markNeedsLayout(); // layout changed
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@override
|
|
132
|
+
void performLayout() {
|
|
133
|
+
size = Size(_radius * 2, _radius * 2);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@override
|
|
137
|
+
void paint(PaintingContext context, Offset offset) {
|
|
138
|
+
final paint = Paint()..color = _color;
|
|
139
|
+
context.canvas.drawCircle(
|
|
140
|
+
offset + Offset(_radius, _radius),
|
|
141
|
+
_radius,
|
|
142
|
+
paint,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**When to use custom RenderObjects:**
|
|
149
|
+
- Rendering hundreds of similar elements (charts, graphs, games)
|
|
150
|
+
- Need sub-millisecond paint performance
|
|
151
|
+
- Widget overhead is measurable in profiling
|
|
152
|
+
|
|
153
|
+
## 3. Platform Channel Optimization
|
|
154
|
+
|
|
155
|
+
### Reduce Serialization Overhead
|
|
156
|
+
|
|
157
|
+
```dart
|
|
158
|
+
// BAD — sending complex nested Maps, large strings
|
|
159
|
+
final result = await platform.invokeMethod('processData', {
|
|
160
|
+
'items': items.map((e) => e.toJson()).toList(), // serializes entire list
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// GOOD — send binary data for large payloads
|
|
164
|
+
final bytes = Uint8List.fromList(utf8.encode(jsonEncode(items)));
|
|
165
|
+
final result = await platform.invokeMethod('processData', bytes);
|
|
166
|
+
|
|
167
|
+
// BEST — use Pigeon for type-safe, efficient platform channels
|
|
168
|
+
// Pigeon generates optimized serialization code at build time
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Batch Platform Calls
|
|
172
|
+
|
|
173
|
+
```dart
|
|
174
|
+
// BAD — N platform channel calls
|
|
175
|
+
for (final file in files) {
|
|
176
|
+
await platform.invokeMethod('processFile', file.path);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// GOOD — single call with batch
|
|
180
|
+
await platform.invokeMethod('processFiles', files.map((f) => f.path).toList());
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Use EventChannel for Streams
|
|
184
|
+
|
|
185
|
+
```dart
|
|
186
|
+
// For continuous data from native (sensors, location, etc.)
|
|
187
|
+
const eventChannel = EventChannel('com.app/sensor_data');
|
|
188
|
+
final stream = eventChannel.receiveBroadcastStream();
|
|
189
|
+
|
|
190
|
+
// In widget
|
|
191
|
+
StreamBuilder<dynamic>(
|
|
192
|
+
stream: stream,
|
|
193
|
+
builder: (context, snapshot) {
|
|
194
|
+
if (!snapshot.hasData) return const SizedBox.shrink();
|
|
195
|
+
return SensorDisplay(data: snapshot.data);
|
|
196
|
+
},
|
|
197
|
+
)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## 4. Isolate Advanced Patterns
|
|
201
|
+
|
|
202
|
+
### Worker Pool for Parallel Processing
|
|
203
|
+
|
|
204
|
+
```dart
|
|
205
|
+
import 'dart:async';
|
|
206
|
+
import 'dart:isolate';
|
|
207
|
+
|
|
208
|
+
class IsolatePool {
|
|
209
|
+
final int size;
|
|
210
|
+
final List<Isolate> _isolates = [];
|
|
211
|
+
final List<SendPort> _sendPorts = [];
|
|
212
|
+
int _nextWorker = 0;
|
|
213
|
+
|
|
214
|
+
IsolatePool(this.size);
|
|
215
|
+
|
|
216
|
+
Future<void> start() async {
|
|
217
|
+
for (var i = 0; i < size; i++) {
|
|
218
|
+
final receivePort = ReceivePort();
|
|
219
|
+
final isolate = await Isolate.spawn(_worker, receivePort.sendPort);
|
|
220
|
+
_isolates.add(isolate);
|
|
221
|
+
_sendPorts.add(await receivePort.first as SendPort);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
Future<R> execute<R>(Function task) async {
|
|
226
|
+
final worker = _sendPorts[_nextWorker % size];
|
|
227
|
+
_nextWorker++;
|
|
228
|
+
final responsePort = ReceivePort();
|
|
229
|
+
worker.send([task, responsePort.sendPort]);
|
|
230
|
+
return await responsePort.first as R;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
void dispose() {
|
|
234
|
+
for (final isolate in _isolates) {
|
|
235
|
+
isolate.kill();
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
static void _worker(SendPort sendPort) {
|
|
240
|
+
final receivePort = ReceivePort();
|
|
241
|
+
sendPort.send(receivePort.sendPort);
|
|
242
|
+
receivePort.listen((message) {
|
|
243
|
+
final task = message[0] as Function;
|
|
244
|
+
final replyPort = message[1] as SendPort;
|
|
245
|
+
replyPort.send(task());
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Isolate with Streaming Results
|
|
252
|
+
|
|
253
|
+
```dart
|
|
254
|
+
// For progress reporting from isolate
|
|
255
|
+
Future<void> processWithProgress(List<String> files) async {
|
|
256
|
+
final receivePort = ReceivePort();
|
|
257
|
+
await Isolate.spawn(_processFiles, _ProcessRequest(files, receivePort.sendPort));
|
|
258
|
+
|
|
259
|
+
await for (final message in receivePort) {
|
|
260
|
+
if (message is double) {
|
|
261
|
+
// Progress update (0.0 - 1.0)
|
|
262
|
+
setState(() => _progress = message);
|
|
263
|
+
} else if (message is List<ProcessedFile>) {
|
|
264
|
+
// Final result
|
|
265
|
+
setState(() => _results = message);
|
|
266
|
+
receivePort.close();
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
static void _processFiles(_ProcessRequest request) {
|
|
272
|
+
final results = <ProcessedFile>[];
|
|
273
|
+
for (var i = 0; i < request.files.length; i++) {
|
|
274
|
+
results.add(_process(request.files[i]));
|
|
275
|
+
request.sendPort.send((i + 1) / request.files.length); // progress
|
|
276
|
+
}
|
|
277
|
+
request.sendPort.send(results); // final result
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## 5. State Management Performance Deep-Dive
|
|
282
|
+
|
|
283
|
+
### Riverpod — select() Granularity
|
|
284
|
+
|
|
285
|
+
```dart
|
|
286
|
+
// ANTI-PATTERN — watching entire provider
|
|
287
|
+
final user = ref.watch(userProvider);
|
|
288
|
+
// Rebuilds when ANY field changes: name, email, avatar, settings...
|
|
289
|
+
|
|
290
|
+
// Level 1 — select single field
|
|
291
|
+
final name = ref.watch(userProvider.select((u) => u.name));
|
|
292
|
+
|
|
293
|
+
// Level 2 — select computed value
|
|
294
|
+
final isAdmin = ref.watch(userProvider.select((u) => u.role == 'admin'));
|
|
295
|
+
|
|
296
|
+
// Level 3 — select multiple fields with record
|
|
297
|
+
final (name, avatar) = ref.watch(
|
|
298
|
+
userProvider.select((u) => (u.name, u.avatarUrl)),
|
|
299
|
+
);
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Provider — Consumer vs context.watch
|
|
303
|
+
|
|
304
|
+
```dart
|
|
305
|
+
// BAD — context.watch at root
|
|
306
|
+
Widget build(BuildContext context) {
|
|
307
|
+
final theme = context.watch<ThemeModel>();
|
|
308
|
+
return MaterialApp(/* ... */); // ENTIRE app rebuilds on theme change
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// GOOD — Consumer wraps only the dependent subtree
|
|
312
|
+
Consumer<ThemeModel>(
|
|
313
|
+
builder: (context, theme, child) {
|
|
314
|
+
return MaterialApp(
|
|
315
|
+
theme: theme.data,
|
|
316
|
+
home: child!, // child NOT rebuilt
|
|
317
|
+
);
|
|
318
|
+
},
|
|
319
|
+
child: const HomePage(),
|
|
320
|
+
)
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Bloc — Event Debouncing
|
|
324
|
+
|
|
325
|
+
```dart
|
|
326
|
+
// Prevent rapid-fire events from causing rebuild storms
|
|
327
|
+
on<SearchChanged>(
|
|
328
|
+
(event, emit) async {
|
|
329
|
+
final results = await _search(event.query);
|
|
330
|
+
emit(state.copyWith(results: results));
|
|
331
|
+
},
|
|
332
|
+
transformer: debounce(const Duration(milliseconds: 300)),
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
EventTransformer<E> debounce<E>(Duration duration) {
|
|
336
|
+
return (events, mapper) => events.debounceTime(duration).flatMap(mapper);
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## 6. Scroll Performance Advanced
|
|
341
|
+
|
|
342
|
+
### AutomaticKeepAliveClientMixin
|
|
343
|
+
|
|
344
|
+
```dart
|
|
345
|
+
// Keep tab content alive when switching tabs (avoid rebuild)
|
|
346
|
+
class _TabContentState extends State<TabContent>
|
|
347
|
+
with AutomaticKeepAliveClientMixin {
|
|
348
|
+
|
|
349
|
+
@override
|
|
350
|
+
bool get wantKeepAlive => true;
|
|
351
|
+
|
|
352
|
+
@override
|
|
353
|
+
Widget build(BuildContext context) {
|
|
354
|
+
super.build(context); // REQUIRED
|
|
355
|
+
return ExpensiveContent();
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### SliverPrototypeExtentList (Variable But Similar Heights)
|
|
361
|
+
|
|
362
|
+
```dart
|
|
363
|
+
// When items have similar but not identical heights
|
|
364
|
+
CustomScrollView(
|
|
365
|
+
slivers: [
|
|
366
|
+
SliverPrototypeExtentList(
|
|
367
|
+
prototypeItem: const ItemTile(item: Item.prototype),
|
|
368
|
+
delegate: SliverChildBuilderDelegate(
|
|
369
|
+
(context, index) => ItemTile(item: items[index]),
|
|
370
|
+
childCount: items.length,
|
|
371
|
+
),
|
|
372
|
+
),
|
|
373
|
+
],
|
|
374
|
+
)
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Pagination with Scroll Controller
|
|
378
|
+
|
|
379
|
+
```dart
|
|
380
|
+
class _ListState extends State<InfiniteList> {
|
|
381
|
+
final _scrollController = ScrollController();
|
|
382
|
+
bool _isLoading = false;
|
|
383
|
+
|
|
384
|
+
@override
|
|
385
|
+
void initState() {
|
|
386
|
+
super.initState();
|
|
387
|
+
_scrollController.addListener(_onScroll);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
void _onScroll() {
|
|
391
|
+
if (_isLoading) return;
|
|
392
|
+
final maxScroll = _scrollController.position.maxScrollExtent;
|
|
393
|
+
final currentScroll = _scrollController.position.pixels;
|
|
394
|
+
if (currentScroll >= maxScroll - 200) { // 200px threshold
|
|
395
|
+
_loadMore();
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
@override
|
|
400
|
+
void dispose() {
|
|
401
|
+
_scrollController.dispose();
|
|
402
|
+
super.dispose();
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
## 7. Animation Advanced Patterns
|
|
408
|
+
|
|
409
|
+
### Staggered Animations
|
|
410
|
+
|
|
411
|
+
```dart
|
|
412
|
+
class _StaggeredState extends State<StaggeredDemo>
|
|
413
|
+
with SingleTickerProviderStateMixin {
|
|
414
|
+
late final AnimationController _controller;
|
|
415
|
+
late final Animation<double> _opacity;
|
|
416
|
+
late final Animation<Offset> _slide;
|
|
417
|
+
late final Animation<double> _scale;
|
|
418
|
+
|
|
419
|
+
@override
|
|
420
|
+
void initState() {
|
|
421
|
+
super.initState();
|
|
422
|
+
_controller = AnimationController(
|
|
423
|
+
vsync: this,
|
|
424
|
+
duration: const Duration(milliseconds: 800),
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
_opacity = Tween(begin: 0.0, end: 1.0).animate(
|
|
428
|
+
CurvedAnimation(parent: _controller, curve: const Interval(0.0, 0.3)),
|
|
429
|
+
);
|
|
430
|
+
_slide = Tween(begin: const Offset(0, 0.3), end: Offset.zero).animate(
|
|
431
|
+
CurvedAnimation(parent: _controller, curve: const Interval(0.1, 0.6, curve: Curves.easeOut)),
|
|
432
|
+
);
|
|
433
|
+
_scale = Tween(begin: 0.8, end: 1.0).animate(
|
|
434
|
+
CurvedAnimation(parent: _controller, curve: const Interval(0.3, 0.8, curve: Curves.easeOut)),
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
_controller.forward();
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
@override
|
|
441
|
+
Widget build(BuildContext context) {
|
|
442
|
+
return AnimatedBuilder(
|
|
443
|
+
animation: _controller,
|
|
444
|
+
builder: (context, child) {
|
|
445
|
+
return Opacity(
|
|
446
|
+
opacity: _opacity.value,
|
|
447
|
+
child: SlideTransition(
|
|
448
|
+
position: _slide,
|
|
449
|
+
child: ScaleTransition(scale: _scale, child: child),
|
|
450
|
+
),
|
|
451
|
+
);
|
|
452
|
+
},
|
|
453
|
+
child: const CardContent(), // cached, not rebuilt
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### Physics-Based Animations (60fps Guaranteed)
|
|
460
|
+
|
|
461
|
+
```dart
|
|
462
|
+
final _spring = SpringSimulation(
|
|
463
|
+
const SpringDescription(mass: 1, stiffness: 200, damping: 15),
|
|
464
|
+
0, // start
|
|
465
|
+
1, // end
|
|
466
|
+
0, // velocity
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
_controller.animateWith(_spring);
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
## 8. CI Performance Benchmarking
|
|
473
|
+
|
|
474
|
+
### Flutter Integration Test Benchmarks
|
|
475
|
+
|
|
476
|
+
```dart
|
|
477
|
+
// test_driver/perf_test.dart
|
|
478
|
+
import 'package:flutter_test/flutter_test.dart';
|
|
479
|
+
import 'package:integration_test/integration_test.dart';
|
|
480
|
+
|
|
481
|
+
void main() {
|
|
482
|
+
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
|
483
|
+
|
|
484
|
+
testWidgets('scrolling performance', (tester) async {
|
|
485
|
+
await tester.pumpWidget(const MyApp());
|
|
486
|
+
|
|
487
|
+
await binding.traceAction(
|
|
488
|
+
() async {
|
|
489
|
+
final listFinder = find.byType(ListView);
|
|
490
|
+
await tester.fling(listFinder, const Offset(0, -500), 1000);
|
|
491
|
+
await tester.pumpAndSettle();
|
|
492
|
+
await tester.fling(listFinder, const Offset(0, 500), 1000);
|
|
493
|
+
await tester.pumpAndSettle();
|
|
494
|
+
},
|
|
495
|
+
reportKey: 'scrolling_timeline',
|
|
496
|
+
);
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
```bash
|
|
502
|
+
# Run benchmark and generate timeline
|
|
503
|
+
flutter drive \
|
|
504
|
+
--driver=test_driver/perf_test.dart \
|
|
505
|
+
--target=integration_test/app_test.dart \
|
|
506
|
+
--profile \
|
|
507
|
+
--no-dds
|
|
508
|
+
|
|
509
|
+
# CI pipeline example (GitHub Actions)
|
|
510
|
+
# Compare against baseline — fail if P99 frame time > 16ms
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### Custom Performance Metrics
|
|
514
|
+
|
|
515
|
+
```dart
|
|
516
|
+
// Track custom metrics with Timeline
|
|
517
|
+
import 'dart:developer';
|
|
518
|
+
|
|
519
|
+
void expensiveOperation() {
|
|
520
|
+
Timeline.startSync('ExpensiveOperation');
|
|
521
|
+
// ... work ...
|
|
522
|
+
Timeline.finishSync();
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Visible in DevTools Performance timeline
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
## 9. Memory Profiling Checklist
|
|
529
|
+
|
|
530
|
+
### Using DevTools Memory View
|
|
531
|
+
|
|
532
|
+
1. **Open Memory tab** in Flutter DevTools
|
|
533
|
+
2. **Take heap snapshot** before and after navigation
|
|
534
|
+
3. **Compare snapshots** — look for objects that should have been GC'd
|
|
535
|
+
4. **Filter by class** — search for your widget/state classes
|
|
536
|
+
5. **Check retaining paths** — find what's holding the reference
|
|
537
|
+
|
|
538
|
+
### Common Leak Patterns & Detection
|
|
539
|
+
|
|
540
|
+
| Symptom | Likely Cause | Detection |
|
|
541
|
+
|---|---|---|
|
|
542
|
+
| Memory grows on repeated navigation | Undisposed controllers | Heap snapshot diff |
|
|
543
|
+
| Memory grows over time | Uncancelled subscriptions | Monitor RSS over 5 min |
|
|
544
|
+
| Sudden memory spikes | Large image decode | Memory timeline |
|
|
545
|
+
| OOM crash on low-end devices | Image cache too large | Monitor max RSS |
|
|
546
|
+
|
|
547
|
+
### Leak Canary Equivalent for Flutter
|
|
548
|
+
|
|
549
|
+
```dart
|
|
550
|
+
// Debug-only leak detection
|
|
551
|
+
assert(() {
|
|
552
|
+
// Track widget disposal
|
|
553
|
+
debugPrint('Widget disposed: $runtimeType');
|
|
554
|
+
return true;
|
|
555
|
+
}());
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
## 10. Production Monitoring
|
|
559
|
+
|
|
560
|
+
### Firebase Performance (Recommended)
|
|
561
|
+
|
|
562
|
+
```dart
|
|
563
|
+
import 'package:firebase_performance/firebase_performance.dart';
|
|
564
|
+
|
|
565
|
+
// Custom trace for critical paths
|
|
566
|
+
final trace = FirebasePerformance.instance.newTrace('checkout_flow');
|
|
567
|
+
await trace.start();
|
|
568
|
+
// ... checkout logic ...
|
|
569
|
+
trace.setMetric('items_count', cart.items.length);
|
|
570
|
+
await trace.stop();
|
|
571
|
+
|
|
572
|
+
// HTTP metric
|
|
573
|
+
final metric = FirebasePerformance.instance.newHttpMetric(url, HttpMethod.Get);
|
|
574
|
+
await metric.start();
|
|
575
|
+
final response = await http.get(Uri.parse(url));
|
|
576
|
+
metric.httpResponseCode = response.statusCode;
|
|
577
|
+
metric.responsePayloadSize = response.contentLength;
|
|
578
|
+
await metric.stop();
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
### Sentry Flutter Performance
|
|
582
|
+
|
|
583
|
+
```dart
|
|
584
|
+
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
585
|
+
|
|
586
|
+
await SentryFlutter.init(
|
|
587
|
+
(options) {
|
|
588
|
+
options.dsn = 'YOUR_DSN';
|
|
589
|
+
options.tracesSampleRate = 0.2;
|
|
590
|
+
options.profilesSampleRate = 0.1;
|
|
591
|
+
options.enableAutoPerformanceTracing = true;
|
|
592
|
+
},
|
|
593
|
+
appRunner: () => runApp(const MyApp()),
|
|
594
|
+
);
|
|
595
|
+
```
|