reis-mobile 0.3.5
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/.claude-plugin/marketplace.json +24 -0
- package/.claude-plugin/plugin.json +12 -0
- package/CHANGELOG.md +103 -0
- package/LICENSE +21 -0
- package/README.md +631 -0
- package/THIRD_PARTY_NOTICES.md +105 -0
- package/agents/flutter-architect.md +92 -0
- package/agents/flutter-performance-engineer.md +103 -0
- package/agents/flutter-test-engineer.md +100 -0
- package/agents/lead-mobile.md +107 -0
- package/agents/mobile-code-reviewer.md +66 -0
- package/agents/mobile-staff-engineer.md +85 -0
- package/agents/plugin-native-expert.md +97 -0
- package/bin/reis-mobile.mjs +256 -0
- package/commands/debate.md +135 -0
- package/commands/doctor.md +23 -0
- package/commands/reis-mobile.md +50 -0
- package/commands/review.md +53 -0
- package/core/config/language.mjs +83 -0
- package/core/context/context-engine.mjs +113 -0
- package/core/detection/project-probe.mjs +104 -0
- package/core/detection/stack-detector.mjs +204 -0
- package/core/diagnostics/doctor.mjs +181 -0
- package/core/install/claude-plugin.mjs +76 -0
- package/core/paths.mjs +4 -0
- package/core/registry/frontmatter.mjs +81 -0
- package/core/registry/registry.mjs +140 -0
- package/core/router/intent-detector.mjs +75 -0
- package/core/router/intents.mjs +91 -0
- package/core/router/router.mjs +83 -0
- package/core/security/redact.mjs +24 -0
- package/package.json +35 -0
- package/skills/architecture-feature-first/SKILL.md +116 -0
- package/skills/bloc/SKILL.md +354 -0
- package/skills/code-review/SKILL.md +89 -0
- package/skills/dart-3-updates/SKILL.md +223 -0
- package/skills/dart-add-unit-test/SKILL.md +126 -0
- package/skills/dart-collect-coverage/SKILL.md +145 -0
- package/skills/dart-fix-runtime-errors/SKILL.md +170 -0
- package/skills/dart-generate-test-mocks/SKILL.md +159 -0
- package/skills/dart-migrate-to-checks-package/SKILL.md +532 -0
- package/skills/dart-resolve-package-conflicts/SKILL.md +120 -0
- package/skills/dart-run-static-analysis/SKILL.md +108 -0
- package/skills/dart-use-pattern-matching/SKILL.md +240 -0
- package/skills/dart-use-pattern-matching/examples/json_patterns.dart +85 -0
- package/skills/detect-mobile-stack/SKILL.md +33 -0
- package/skills/effective-dart/SKILL.md +187 -0
- package/skills/firebase-ai/SKILL.md +93 -0
- package/skills/firebase-analytics/SKILL.md +129 -0
- package/skills/firebase-app-check/SKILL.md +124 -0
- package/skills/firebase-auth/SKILL.md +224 -0
- package/skills/firebase-cloud-firestore/SKILL.md +165 -0
- package/skills/firebase-cloud-functions/SKILL.md +148 -0
- package/skills/firebase-crashlytics/SKILL.md +182 -0
- package/skills/firebase-data-connect/SKILL.md +80 -0
- package/skills/firebase-database/SKILL.md +224 -0
- package/skills/firebase-in-app-messaging/SKILL.md +111 -0
- package/skills/firebase-messaging/SKILL.md +201 -0
- package/skills/firebase-remote-config/SKILL.md +131 -0
- package/skills/firebase-storage/SKILL.md +158 -0
- package/skills/flutter-add-integration-test/SKILL.md +167 -0
- package/skills/flutter-add-widget-preview/SKILL.md +149 -0
- package/skills/flutter-add-widget-test/SKILL.md +158 -0
- package/skills/flutter-adding-home-screen-widgets/SKILL.md +205 -0
- package/skills/flutter-animating-apps/SKILL.md +185 -0
- package/skills/flutter-app-architecture/SKILL.md +80 -0
- package/skills/flutter-apply-architecture-best-practices/SKILL.md +166 -0
- package/skills/flutter-build-responsive-layout/SKILL.md +143 -0
- package/skills/flutter-building-forms/SKILL.md +125 -0
- package/skills/flutter-building-layouts/SKILL.md +131 -0
- package/skills/flutter-building-plugins/SKILL.md +209 -0
- package/skills/flutter-caching-data/SKILL.md +171 -0
- package/skills/flutter-change-notifier/SKILL.md +138 -0
- package/skills/flutter-embedding-native-views/SKILL.md +200 -0
- package/skills/flutter-errors/SKILL.md +126 -0
- package/skills/flutter-fix-layout-issues/SKILL.md +134 -0
- package/skills/flutter-handling-concurrency/SKILL.md +182 -0
- package/skills/flutter-implement-json-serialization/SKILL.md +157 -0
- package/skills/flutter-improving-accessibility/SKILL.md +113 -0
- package/skills/flutter-interoperating-with-native-apis/SKILL.md +130 -0
- package/skills/flutter-login-usecase/SKILL.md +38 -0
- package/skills/flutter-managing-state/SKILL.md +206 -0
- package/skills/flutter-project-audit/SKILL.md +66 -0
- package/skills/flutter-reducing-app-size/SKILL.md +103 -0
- package/skills/flutter-secure-token-store/SKILL.md +35 -0
- package/skills/flutter-setting-up-on-macos/SKILL.md +78 -0
- package/skills/flutter-setup-declarative-routing/SKILL.md +259 -0
- package/skills/flutter-setup-localization/SKILL.md +214 -0
- package/skills/flutter-theming-apps/SKILL.md +163 -0
- package/skills/flutter-use-http-package/SKILL.md +178 -0
- package/skills/flutter-widget-review/SKILL.md +54 -0
- package/skills/flutter-working-with-databases/SKILL.md +203 -0
- package/skills/flutterfire-configure/SKILL.md +143 -0
- package/skills/mobile-security-audit/SKILL.md +101 -0
- package/skills/mockito/SKILL.md +149 -0
- package/skills/mocktail/SKILL.md +122 -0
- package/skills/patrol-e2e-testing/SKILL.md +224 -0
- package/skills/provider/SKILL.md +139 -0
- package/skills/riverpod/SKILL.md +246 -0
- package/skills/testing/SKILL.md +50 -0
- package/stacks/android/stack.json +8 -0
- package/stacks/flutter/stack.json +8 -0
- package/stacks/ios/stack.json +8 -0
- package/stacks/kotlin-multiplatform/stack.json +8 -0
- package/stacks/react-native/stack.json +8 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: flutter-animating-apps
|
|
3
|
+
description: Implements animated effects, transitions, and motion in a Flutter app. Use when adding visual feedback, shared element transitions, or physics-based animations.
|
|
4
|
+
metadata:
|
|
5
|
+
model: models/gemini-3.1-pro-preview
|
|
6
|
+
last_modified: Thu, 12 Mar 2026 22:16:34 GMT
|
|
7
|
+
routing: manual
|
|
8
|
+
stacks: [flutter]
|
|
9
|
+
source: https://github.com/flutter/skills
|
|
10
|
+
license: BSD-3-Clause
|
|
11
|
+
---
|
|
12
|
+
# Implementing Flutter Animations
|
|
13
|
+
|
|
14
|
+
## Contents
|
|
15
|
+
- [Core Concepts](#core-concepts)
|
|
16
|
+
- [Animation Strategies](#animation-strategies)
|
|
17
|
+
- [Workflows](#workflows)
|
|
18
|
+
- [Implementing Implicit Animations](#implementing-implicit-animations)
|
|
19
|
+
- [Implementing Explicit Animations](#implementing-explicit-animations)
|
|
20
|
+
- [Implementing Hero Transitions](#implementing-hero-transitions)
|
|
21
|
+
- [Implementing Physics-Based Animations](#implementing-physics-based-animations)
|
|
22
|
+
- [Examples](#examples)
|
|
23
|
+
|
|
24
|
+
## Core Concepts
|
|
25
|
+
|
|
26
|
+
Manage Flutter animations using the core typed `Animation` system. Do not manually calculate frames; rely on the framework's ticker and interpolation classes.
|
|
27
|
+
|
|
28
|
+
* **`Animation<T>`**: Treat this as an abstract representation of a value that changes over time. It holds state (completed, dismissed) and notifies listeners, but knows nothing about the UI.
|
|
29
|
+
* **`AnimationController`**: Instantiate this to drive the animation. It generates values (typically 0.0 to 1.0) tied to the screen refresh rate. Always provide a `vsync` (usually via `SingleTickerProviderStateMixin`) to prevent offscreen resource consumption. Always `dispose()` controllers to prevent memory leaks.
|
|
30
|
+
* **`Tween<T>`**: Define a stateless mapping from an input range (usually 0.0-1.0) to an output type (e.g., `Color`, `Offset`, `double`). Chain tweens with curves using `.animate()`.
|
|
31
|
+
* **`Curve`**: Apply non-linear timing (e.g., `Curves.easeIn`, `Curves.bounceOut`) to an animation using a `CurvedAnimation` or `CurveTween`.
|
|
32
|
+
|
|
33
|
+
## Animation Strategies
|
|
34
|
+
|
|
35
|
+
Apply conditional logic to select the correct animation approach:
|
|
36
|
+
|
|
37
|
+
* **If animating simple property changes (size, color, opacity) without playback control:** Use **Implicit Animations** (e.g., `AnimatedContainer`, `AnimatedOpacity`, `TweenAnimationBuilder`).
|
|
38
|
+
* **If requiring playback control (play, pause, reverse, loop) or coordinating multiple properties:** Use **Explicit Animations** (e.g., `AnimationController` with `AnimatedBuilder` or `AnimatedWidget`).
|
|
39
|
+
* **If animating elements between two distinct routes:** Use **Hero Animations** (Shared Element Transitions).
|
|
40
|
+
* **If modeling real-world motion (e.g., snapping back after a drag):** Use **Physics-Based Animations** (e.g., `SpringSimulation`).
|
|
41
|
+
* **If animating a sequence of overlapping or delayed motions:** Use **Staggered Animations** (multiple `Tween`s driven by a single `AnimationController` using `Interval` curves).
|
|
42
|
+
|
|
43
|
+
## Workflows
|
|
44
|
+
|
|
45
|
+
### Implementing Implicit Animations
|
|
46
|
+
|
|
47
|
+
Use this workflow for "fire-and-forget" state-driven animations.
|
|
48
|
+
|
|
49
|
+
- [ ] **Task Progress:**
|
|
50
|
+
- [ ] Identify the target properties to animate (e.g., width, color).
|
|
51
|
+
- [ ] Replace the static widget (e.g., `Container`) with its animated counterpart (e.g., `AnimatedContainer`).
|
|
52
|
+
- [ ] Define the `duration` property.
|
|
53
|
+
- [ ] (Optional) Define the `curve` property for non-linear motion.
|
|
54
|
+
- [ ] Trigger the animation by updating the properties inside a `setState()` call.
|
|
55
|
+
- [ ] Run validator -> review UI for jank -> adjust duration/curve if necessary.
|
|
56
|
+
|
|
57
|
+
### Implementing Explicit Animations
|
|
58
|
+
|
|
59
|
+
Use this workflow when you need granular control over the animation lifecycle.
|
|
60
|
+
|
|
61
|
+
- [ ] **Task Progress:**
|
|
62
|
+
- [ ] Add `SingleTickerProviderStateMixin` (or `TickerProviderStateMixin` for multiple controllers) to the `State` class.
|
|
63
|
+
- [ ] Initialize an `AnimationController` in `initState()`, providing `vsync: this` and a `duration`.
|
|
64
|
+
- [ ] Define a `Tween` and chain it to the controller using `.animate()`.
|
|
65
|
+
- [ ] Wrap the target UI in an `AnimatedBuilder` (preferred for complex trees) or subclass `AnimatedWidget`.
|
|
66
|
+
- [ ] Pass the `Animation` object to the `AnimatedBuilder`'s `animation` property.
|
|
67
|
+
- [ ] Control playback using `controller.forward()`, `controller.reverse()`, or `controller.repeat()`.
|
|
68
|
+
- [ ] Call `controller.dispose()` in the `dispose()` method.
|
|
69
|
+
- [ ] Run validator -> check for memory leaks -> ensure `dispose()` is called.
|
|
70
|
+
|
|
71
|
+
### Implementing Hero Transitions
|
|
72
|
+
|
|
73
|
+
Use this workflow to fly a widget between two routes.
|
|
74
|
+
|
|
75
|
+
- [ ] **Task Progress:**
|
|
76
|
+
- [ ] Wrap the source widget in a `Hero` widget.
|
|
77
|
+
- [ ] Assign a unique, data-driven `tag` to the source `Hero`.
|
|
78
|
+
- [ ] Wrap the destination widget in a `Hero` widget.
|
|
79
|
+
- [ ] Assign the *exact same* `tag` to the destination `Hero`.
|
|
80
|
+
- [ ] Ensure the widget trees inside both `Hero` widgets are visually similar to prevent jarring jumps.
|
|
81
|
+
- [ ] Trigger the transition by pushing the destination route via `Navigator`.
|
|
82
|
+
|
|
83
|
+
### Implementing Physics-Based Animations
|
|
84
|
+
|
|
85
|
+
Use this workflow for gesture-driven, natural motion.
|
|
86
|
+
|
|
87
|
+
- [ ] **Task Progress:**
|
|
88
|
+
- [ ] Set up an `AnimationController` (do not set a fixed duration).
|
|
89
|
+
- [ ] Capture gesture velocity using a `GestureDetector` (e.g., `onPanEnd` providing `DragEndDetails`).
|
|
90
|
+
- [ ] Convert the pixel velocity to the coordinate space of the animating property.
|
|
91
|
+
- [ ] Instantiate a `SpringSimulation` with mass, stiffness, damping, and the calculated velocity.
|
|
92
|
+
- [ ] Drive the controller using `controller.animateWith(simulation)`.
|
|
93
|
+
|
|
94
|
+
## Examples
|
|
95
|
+
|
|
96
|
+
<details>
|
|
97
|
+
<summary><b>Example: Explicit Animation (Staggered with AnimatedBuilder)</b></summary>
|
|
98
|
+
|
|
99
|
+
```dart
|
|
100
|
+
class StaggeredAnimationDemo extends StatefulWidget {
|
|
101
|
+
@override
|
|
102
|
+
State<StaggeredAnimationDemo> createState() => _StaggeredAnimationDemoState();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
class _StaggeredAnimationDemoState extends State<StaggeredAnimationDemo> with SingleTickerProviderStateMixin {
|
|
106
|
+
late AnimationController _controller;
|
|
107
|
+
late Animation<double> _widthAnimation;
|
|
108
|
+
late Animation<Color?> _colorAnimation;
|
|
109
|
+
|
|
110
|
+
@override
|
|
111
|
+
void initState() {
|
|
112
|
+
super.initState();
|
|
113
|
+
_controller = AnimationController(
|
|
114
|
+
duration: const Duration(seconds: 2),
|
|
115
|
+
vsync: this,
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// Staggered width animation (0.0 to 0.5 interval)
|
|
119
|
+
_widthAnimation = Tween<double>(begin: 50.0, end: 200.0).animate(
|
|
120
|
+
CurvedAnimation(
|
|
121
|
+
parent: _controller,
|
|
122
|
+
curve: const Interval(0.0, 0.5, curve: Curves.easeIn),
|
|
123
|
+
),
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// Staggered color animation (0.5 to 1.0 interval)
|
|
127
|
+
_colorAnimation = ColorTween(begin: Colors.blue, end: Colors.red).animate(
|
|
128
|
+
CurvedAnimation(
|
|
129
|
+
parent: _controller,
|
|
130
|
+
curve: const Interval(0.5, 1.0, curve: Curves.easeOut),
|
|
131
|
+
),
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
_controller.forward();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@override
|
|
138
|
+
void dispose() {
|
|
139
|
+
_controller.dispose(); // CRITICAL: Prevent memory leaks
|
|
140
|
+
super.dispose();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
@override
|
|
144
|
+
Widget build(BuildContext context) {
|
|
145
|
+
return AnimatedBuilder(
|
|
146
|
+
animation: _controller,
|
|
147
|
+
builder: (context, child) {
|
|
148
|
+
return Container(
|
|
149
|
+
width: _widthAnimation.value,
|
|
150
|
+
height: 50.0,
|
|
151
|
+
color: _colorAnimation.value,
|
|
152
|
+
);
|
|
153
|
+
},
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
</details>
|
|
159
|
+
|
|
160
|
+
<details>
|
|
161
|
+
<summary><b>Example: Custom Page Route Transition</b></summary>
|
|
162
|
+
|
|
163
|
+
```dart
|
|
164
|
+
Route createCustomRoute(Widget destination) {
|
|
165
|
+
return PageRouteBuilder(
|
|
166
|
+
pageBuilder: (context, animation, secondaryAnimation) => destination,
|
|
167
|
+
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
|
168
|
+
const begin = Offset(0.0, 1.0); // Start from bottom
|
|
169
|
+
const end = Offset.zero;
|
|
170
|
+
const curve = Curves.easeOut;
|
|
171
|
+
|
|
172
|
+
final tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
|
|
173
|
+
final offsetAnimation = animation.drive(tween);
|
|
174
|
+
|
|
175
|
+
return SlideTransition(
|
|
176
|
+
position: offsetAnimation,
|
|
177
|
+
child: child,
|
|
178
|
+
);
|
|
179
|
+
},
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Usage: Navigator.of(context).push(createCustomRoute(const NextPage()));
|
|
184
|
+
```
|
|
185
|
+
</details>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: flutter-app-architecture
|
|
3
|
+
description: Provides best practices for Flutter app architecture, including layered architecture, data flow, state management patterns, and extensibility guidelines.
|
|
4
|
+
intents: [architecture]
|
|
5
|
+
stacks: [flutter]
|
|
6
|
+
source: https://github.com/evanca/flutter-ai-rules
|
|
7
|
+
license: MIT
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Flutter App Architecture Skill
|
|
11
|
+
|
|
12
|
+
This skill provides comprehensive guidelines for structuring Flutter applications using layered architecture, proper data flow, and best practices for maintainability and testability.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Architecture
|
|
17
|
+
|
|
18
|
+
1. Separate your features into a UI Layer (presentation), a Data Layer (business data and logic), and, for complex apps, consider adding a Domain (Logic) Layer between UI and Data layers to encapsulate business logic and use-cases.
|
|
19
|
+
2. You can organize code by feature: The classes needed for each feature are grouped together. For example, you might have an auth directory, which would contain files like auth_viewmodel.dart (or, depending on your state management approach: auth_controller.dart, auth_provider.dart, auth_bloc.dart), login_usecase.dart, logout_usecase.dart, login_screen.dart, logout_button.dart, etc. Alternatively, you can organize by type or use a hybrid approach.
|
|
20
|
+
3. Only allow communication between adjacent layers; the UI layer should not access the data layer directly, and vice versa.
|
|
21
|
+
4. Introduce a Logic (Domain) Layer only for complex business logic that does not fit cleanly in the UI or Data layers.
|
|
22
|
+
5. Clearly define the responsibilities, boundaries, and interfaces of each layer and component (Views, View Models, Repositories, Services).
|
|
23
|
+
6. Further divide each layer into components with specific responsibilities and well-defined interfaces.
|
|
24
|
+
7. In the UI Layer, use Views to describe how to present data to the user; keep logic minimal and only UI-related.
|
|
25
|
+
8. Pass events from Views to View Models in response to user interactions.
|
|
26
|
+
9. In View Models, contain logic to convert app data into UI state and maintain the current state needed by the view.
|
|
27
|
+
10. Expose callbacks (commands) from View Models to Views and retrieve/transform data from repositories.
|
|
28
|
+
11. In the Data Layer, use Repositories as the single source of truth (SSOT) for model data and to handle business logic such as caching, error handling, and refreshing data.
|
|
29
|
+
12. Only the SSOT class (usually the repository) should be able to mutate its data; all other classes should read from it.
|
|
30
|
+
13. Repositories should transform raw data from services into domain models and output data consumed by View Models.
|
|
31
|
+
14. Use Services to wrap API endpoints and expose asynchronous response objects; services should isolate data-loading and hold no state.
|
|
32
|
+
15. Use dependency injection to provide components with their dependencies, enabling testability and flexibility.
|
|
33
|
+
|
|
34
|
+
## Data Flow and State
|
|
35
|
+
|
|
36
|
+
1. Follow unidirectional data flow: state flows from the data layer through the logic layer to the UI layer, and events from user interaction flow in the opposite direction.
|
|
37
|
+
2. Data changes should always happen in the SSOT (data layer), not in the UI or logic layers.
|
|
38
|
+
3. The UI should always reflect the current (immutable) state; trigger UI rebuilds only in response to state changes.
|
|
39
|
+
4. Views should contain as little logic as possible and be driven by state from View Models.
|
|
40
|
+
|
|
41
|
+
## Use Cases / Interactors
|
|
42
|
+
|
|
43
|
+
1. Introduce use cases/interactors in the domain layer only when logic is complex, reused, or merges data from multiple repositories.
|
|
44
|
+
2. Use cases depend on repositories and may be used by multiple view models.
|
|
45
|
+
3. Add use cases only when needed; refactor to use use-cases exclusively if logic is repeatedly shared across view models.
|
|
46
|
+
|
|
47
|
+
## Extensibility and Testability
|
|
48
|
+
|
|
49
|
+
1. All architectural components should have well-defined inputs and outputs (interfaces).
|
|
50
|
+
2. Favor dependency injection to allow swapping implementations without changing consumers.
|
|
51
|
+
3. Test view models by mocking repositories; test UI logic independently of widgets.
|
|
52
|
+
4. Design components to be easily replaceable and independently testable.
|
|
53
|
+
|
|
54
|
+
## Best Practices
|
|
55
|
+
|
|
56
|
+
1. Strongly recommend following separation of concerns and layered architecture.
|
|
57
|
+
2. Strongly recommend using dependency injection for testability and flexibility.
|
|
58
|
+
3. Recommend using MVVM as the default pattern, but adapt as needed for your app's complexity.
|
|
59
|
+
4. Use key-value storage for simple data (e.g., configuration, preferences) and SQL storage for complex relationships.
|
|
60
|
+
5. Use optimistic updates to improve perceived responsiveness by updating the UI before operations complete.
|
|
61
|
+
6. Support offline-first strategies by combining local and remote data sources in repositories and enabling synchronization as appropriate.
|
|
62
|
+
7. Keep views focused on presentation and extract reusable widgets into separate components.
|
|
63
|
+
8. Use `StatelessWidget` when possible and avoid unnecessary `StatefulWidget`s.
|
|
64
|
+
9. Keep build methods simple and focused on rendering.
|
|
65
|
+
10. Choose state management approaches appropriate to the complexity of your app.
|
|
66
|
+
11. Keep state as local as possible to minimize rebuilds and complexity.
|
|
67
|
+
12. Use `const` constructors when possible to improve performance.
|
|
68
|
+
13. Avoid expensive operations in build methods and implement pagination for large lists.
|
|
69
|
+
14. Keep files focused on a single responsibility and limit file length for readability.
|
|
70
|
+
15. Group related functionality together and use `final` for fields and top-level variables when possible.
|
|
71
|
+
16. Prefer making declarations private and consider making constructors `const` if the class supports it.
|
|
72
|
+
17. Follow Dart naming conventions and format code using `dart format`.
|
|
73
|
+
18. Use curly braces for all flow control statements to ensure clarity and prevent bugs.
|
|
74
|
+
19. Prefer explicit typing and generics on public APIs (for example, prefer typed command signatures such as `Command0<void>` rather than untyped/dynamic signatures) to improve clarity and type safety.
|
|
75
|
+
20. For small immutable domain or data models, prefer using `abstract class` with `const` constructors and `final` fields where it improves readability and enforces immutability.
|
|
76
|
+
21. Use descriptive constant names for resources and table identifiers (for example prefer `_todoTableName` over compact prefixes like `_kTableTodo`) to improve clarity across examples and migrations.
|
|
77
|
+
|
|
78
|
+
## References
|
|
79
|
+
|
|
80
|
+
- [Flutter Website GitHub Repository](https://github.com/flutter/website)
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: flutter-apply-architecture-best-practices
|
|
3
|
+
description: Architects a Flutter application using the recommended layered approach (UI, Logic, Data). Use when structuring a new project or refactoring for scalability.
|
|
4
|
+
metadata:
|
|
5
|
+
model: models/gemini-3.1-pro-preview
|
|
6
|
+
last_modified: Tue, 21 Apr 2026 20:11:20 GMT
|
|
7
|
+
intents: [architecture]
|
|
8
|
+
stacks: [flutter]
|
|
9
|
+
source: https://github.com/flutter/skills
|
|
10
|
+
license: BSD-3-Clause
|
|
11
|
+
---
|
|
12
|
+
# Architecting Flutter Applications
|
|
13
|
+
|
|
14
|
+
## Contents
|
|
15
|
+
- [Architectural Layers](#architectural-layers)
|
|
16
|
+
- [Project Structure](#project-structure)
|
|
17
|
+
- [Workflow: Implementing a New Feature](#workflow-implementing-a-new-feature)
|
|
18
|
+
- [Examples](#examples)
|
|
19
|
+
|
|
20
|
+
## Architectural Layers
|
|
21
|
+
|
|
22
|
+
Enforce strict Separation of Concerns by dividing the application into distinct layers. Never mix UI rendering with business logic or data fetching.
|
|
23
|
+
|
|
24
|
+
### UI Layer (Presentation)
|
|
25
|
+
Implement the MVVM (Model-View-ViewModel) pattern to manage UI state and logic.
|
|
26
|
+
* **Views:** Write reusable, lean widgets. Restrict logic in Views to UI-specific operations (e.g., animations, layout constraints, simple routing). Pass all required data from the ViewModel.
|
|
27
|
+
* **ViewModels:** Manage UI state and handle user interactions. Extend `ChangeNotifier` (or use `Listenable`) to expose state. Expose immutable state snapshots to the View. Inject Repositories into ViewModels via the constructor.
|
|
28
|
+
|
|
29
|
+
### Data Layer
|
|
30
|
+
Implement the Repository pattern to isolate data access logic and create a single source of truth.
|
|
31
|
+
* **Services:** Create stateless classes to wrap external APIs (HTTP clients, local databases, platform plugins). Return raw API models or `Result` wrappers.
|
|
32
|
+
* **Repositories:** Consume one or more Services. Transform raw API models into clean Domain Models. Handle caching, offline synchronization, and retry logic. Expose Domain Models to ViewModels.
|
|
33
|
+
|
|
34
|
+
### Logic Layer (Domain - Optional)
|
|
35
|
+
* **Use Cases:** Implement this layer only if the application contains complex business logic that clutters the ViewModel, or if logic must be reused across multiple ViewModels. Extract this logic into dedicated Use Case (interactor) classes that sit between ViewModels and Repositories.
|
|
36
|
+
|
|
37
|
+
## Project Structure
|
|
38
|
+
|
|
39
|
+
Organize the codebase using a hybrid approach: group UI components by feature, and group Data/Domain components by type.
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
lib/
|
|
43
|
+
├── data/
|
|
44
|
+
│ ├── models/ # API models
|
|
45
|
+
│ ├── repositories/ # Repository implementations
|
|
46
|
+
│ └── services/ # API clients, local storage wrappers
|
|
47
|
+
├── domain/
|
|
48
|
+
│ ├── models/ # Clean domain models
|
|
49
|
+
│ └── use_cases/ # Optional business logic classes
|
|
50
|
+
└── ui/
|
|
51
|
+
├── core/ # Shared widgets, themes, typography
|
|
52
|
+
└── features/
|
|
53
|
+
└── [feature_name]/
|
|
54
|
+
├── view_models/
|
|
55
|
+
└── views/
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Workflow: Implementing a New Feature
|
|
59
|
+
|
|
60
|
+
Follow this sequential workflow when adding a new feature to the application. Copy the checklist to track progress.
|
|
61
|
+
|
|
62
|
+
### Task Progress
|
|
63
|
+
- [ ] **Step 1: Define Domain Models.** Create immutable data classes for the feature using `freezed` or `built_value`.
|
|
64
|
+
- [ ] **Step 2: Implement Services.** Create or update Service classes to handle external API communication.
|
|
65
|
+
- [ ] **Step 3: Implement Repositories.** Create the Repository to consume Services and return Domain Models.
|
|
66
|
+
- [ ] **Step 4: Apply Conditional Logic (Domain Layer).**
|
|
67
|
+
- *If the feature requires complex data transformation or cross-repository logic:* Create a Use Case class.
|
|
68
|
+
- *If the feature is a simple CRUD operation:* Skip to Step 5.
|
|
69
|
+
- [ ] **Step 5: Implement the ViewModel.** Create the ViewModel extending `ChangeNotifier`. Inject required Repositories/Use Cases. Expose immutable state and command methods.
|
|
70
|
+
- [ ] **Step 6: Implement the View.** Create the UI widget. Use `ListenableBuilder` or `AnimatedBuilder` to listen to ViewModel changes.
|
|
71
|
+
- [ ] **Step 7: Inject Dependencies.** Register the new Service, Repository, and ViewModel in the dependency injection container (e.g., `provider` or `get_it`).
|
|
72
|
+
- [ ] **Step 8: Run Validator.** Execute unit tests for the ViewModel and Repository.
|
|
73
|
+
- *Feedback Loop:* Run tests -> Review failures -> Fix logic -> Re-run until passing.
|
|
74
|
+
|
|
75
|
+
## Examples
|
|
76
|
+
|
|
77
|
+
### Data Layer: Service and Repository
|
|
78
|
+
|
|
79
|
+
```dart
|
|
80
|
+
// 1. Service (Raw API interaction)
|
|
81
|
+
class ApiClient {
|
|
82
|
+
Future<UserApiModel> fetchUser(String id) async {
|
|
83
|
+
// HTTP GET implementation...
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 2. Repository (Single source of truth, returns Domain Model)
|
|
88
|
+
class UserRepository {
|
|
89
|
+
UserRepository({required ApiClient apiClient}) : _apiClient = apiClient;
|
|
90
|
+
|
|
91
|
+
final ApiClient _apiClient;
|
|
92
|
+
User? _cachedUser;
|
|
93
|
+
|
|
94
|
+
Future<User> getUser(String id) async {
|
|
95
|
+
if (_cachedUser != null) return _cachedUser!;
|
|
96
|
+
|
|
97
|
+
final apiModel = await _apiClient.fetchUser(id);
|
|
98
|
+
_cachedUser = User(id: apiModel.id, name: apiModel.fullName); // Transform to Domain Model
|
|
99
|
+
return _cachedUser!;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### UI Layer: ViewModel and View
|
|
105
|
+
|
|
106
|
+
```dart
|
|
107
|
+
// 3. ViewModel (State management and presentation logic)
|
|
108
|
+
class ProfileViewModel extends ChangeNotifier {
|
|
109
|
+
ProfileViewModel({required UserRepository userRepository})
|
|
110
|
+
: _userRepository = userRepository;
|
|
111
|
+
|
|
112
|
+
final UserRepository _userRepository;
|
|
113
|
+
|
|
114
|
+
User? _user;
|
|
115
|
+
User? get user => _user;
|
|
116
|
+
|
|
117
|
+
bool _isLoading = false;
|
|
118
|
+
bool get isLoading => _isLoading;
|
|
119
|
+
|
|
120
|
+
Future<void> loadProfile(String id) async {
|
|
121
|
+
_isLoading = true;
|
|
122
|
+
notifyListeners();
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
_user = await _userRepository.getUser(id);
|
|
126
|
+
} finally {
|
|
127
|
+
_isLoading = false;
|
|
128
|
+
notifyListeners();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 4. View (Dumb UI component)
|
|
134
|
+
class ProfileView extends StatelessWidget {
|
|
135
|
+
const ProfileView({super.key, required this.viewModel});
|
|
136
|
+
|
|
137
|
+
final ProfileViewModel viewModel;
|
|
138
|
+
|
|
139
|
+
@override
|
|
140
|
+
Widget build(BuildContext context) {
|
|
141
|
+
return ListenableBuilder(
|
|
142
|
+
listenable: viewModel,
|
|
143
|
+
builder: (context, _) {
|
|
144
|
+
if (viewModel.isLoading) {
|
|
145
|
+
return const Center(child: CircularProgressIndicator());
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
final user = viewModel.user;
|
|
149
|
+
if (user == null) {
|
|
150
|
+
return const Center(child: Text('User not found'));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return Column(
|
|
154
|
+
children: [
|
|
155
|
+
Text(user.name),
|
|
156
|
+
ElevatedButton(
|
|
157
|
+
onPressed: () => viewModel.loadProfile(user.id),
|
|
158
|
+
child: const Text('Refresh'),
|
|
159
|
+
),
|
|
160
|
+
],
|
|
161
|
+
);
|
|
162
|
+
},
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: flutter-build-responsive-layout
|
|
3
|
+
description: Use `LayoutBuilder`, `MediaQuery`, or `Expanded/Flexible` to create a layout that adapts to different screen sizes. Use when you need the UI to look good on both mobile and tablet/desktop form factors.
|
|
4
|
+
metadata:
|
|
5
|
+
model: models/gemini-3.1-pro-preview
|
|
6
|
+
last_modified: Tue, 21 Apr 2026 20:17:40 GMT
|
|
7
|
+
routing: manual
|
|
8
|
+
stacks: [flutter]
|
|
9
|
+
source: https://github.com/flutter/skills
|
|
10
|
+
license: BSD-3-Clause
|
|
11
|
+
---
|
|
12
|
+
# Implementing Adaptive Layouts
|
|
13
|
+
|
|
14
|
+
## Contents
|
|
15
|
+
- [Space Measurement Guidelines](#space-measurement-guidelines)
|
|
16
|
+
- [Widget Sizing and Constraints](#widget-sizing-and-constraints)
|
|
17
|
+
- [Device and Orientation Behaviors](#device-and-orientation-behaviors)
|
|
18
|
+
- [Workflow: Constructing an Adaptive Layout](#workflow-constructing-an-adaptive-layout)
|
|
19
|
+
- [Workflow: Optimizing for Large Screens](#workflow-optimizing-for-large-screens)
|
|
20
|
+
- [Examples](#examples)
|
|
21
|
+
|
|
22
|
+
## Space Measurement Guidelines
|
|
23
|
+
Determine the available space accurately to ensure layouts adapt to the app window, not just the physical device.
|
|
24
|
+
|
|
25
|
+
* **Use `MediaQuery.sizeOf(context)`** to get the size of the entire app window.
|
|
26
|
+
* **Use `LayoutBuilder`** to make layout decisions based on the parent widget's allocated space. Evaluate `constraints.maxWidth` to determine the appropriate widget tree to return.
|
|
27
|
+
* **Do not use `MediaQuery.orientationOf` or `OrientationBuilder`** near the top of the widget tree to switch layouts. Device orientation does not accurately reflect the available app window space.
|
|
28
|
+
* **Do not check for hardware types** (e.g., "phone" vs. "tablet"). Flutter apps run in resizable windows, multi-window modes, and picture-in-picture. Base all layout decisions strictly on available window space.
|
|
29
|
+
|
|
30
|
+
## Widget Sizing and Constraints
|
|
31
|
+
Understand and apply Flutter's core layout rule: **Constraints go down. Sizes go up. Parent sets position.**
|
|
32
|
+
|
|
33
|
+
* **Distribute Space:** Use `Expanded` and `Flexible` within `Row`, `Column`, or `Flex` widgets.
|
|
34
|
+
* Use `Expanded` to force a child to fill all remaining available space (equivalent to `Flexible` with `fit: FlexFit.tight` and a `flex` factor of 1.0).
|
|
35
|
+
* Use `Flexible` to allow a child to size itself up to a specific limit while still expanding/contracting. Use the `flex` factor to define the ratio of space consumption among siblings.
|
|
36
|
+
* **Constrain Width:** Prevent widgets from consuming all horizontal space on large screens. Wrap widgets like `GridView` or `ListView` in a `ConstrainedBox` or `Container` and define a `maxWidth` in the `BoxConstraints`.
|
|
37
|
+
* **Lazy Rendering:** Always use `ListView.builder` or `GridView.builder` when rendering lists with an unknown or large number of items.
|
|
38
|
+
|
|
39
|
+
## Device and Orientation Behaviors
|
|
40
|
+
Ensure the app behaves correctly across all device form factors and input methods.
|
|
41
|
+
|
|
42
|
+
* **Do not lock screen orientation.** Locking orientation causes severe layout issues on foldable devices, often resulting in letterboxing (the app centered with black borders). Android large format tiers require both portrait and landscape support.
|
|
43
|
+
* **Fallback for Locked Orientation:** If business requirements strictly mandate a locked orientation, use the `Display API` to retrieve physical screen dimensions instead of `MediaQuery`. `MediaQuery` fails to receive the larger window size in compatibility modes.
|
|
44
|
+
* **Support Multiple Inputs:** Implement support for basic mice, trackpads, and keyboard shortcuts. Ensure touch targets are appropriately sized and keyboard navigation is accessible.
|
|
45
|
+
|
|
46
|
+
## Workflow: Constructing an Adaptive Layout
|
|
47
|
+
|
|
48
|
+
Follow this workflow to implement a layout that adapts to the available `BoxConstraints`.
|
|
49
|
+
|
|
50
|
+
**Task Progress:**
|
|
51
|
+
- [ ] Identify the target widget that requires adaptive behavior.
|
|
52
|
+
- [ ] Wrap the widget tree in a `LayoutBuilder`.
|
|
53
|
+
- [ ] Extract the `constraints.maxWidth` from the builder callback.
|
|
54
|
+
- [ ] Define an adaptive breakpoint (e.g., `largeScreenMinWidth = 600`).
|
|
55
|
+
- [ ] **If `maxWidth > largeScreenMinWidth`:** Return a large-screen layout (e.g., a `Row` placing a navigation sidebar and content area side-by-side).
|
|
56
|
+
- [ ] **If `maxWidth <= largeScreenMinWidth`:** Return a small-screen layout (e.g., a `Column` or standard navigation-style approach).
|
|
57
|
+
- [ ] Run validator -> resize the application window -> review layout transitions -> fix overflow errors.
|
|
58
|
+
|
|
59
|
+
## Workflow: Optimizing for Large Screens
|
|
60
|
+
|
|
61
|
+
Follow this workflow to prevent UI elements from stretching unnaturally on large displays.
|
|
62
|
+
|
|
63
|
+
**Task Progress:**
|
|
64
|
+
- [ ] Identify full-width components (e.g., `ListView`, text blocks, forms).
|
|
65
|
+
- [ ] **If optimizing a list:** Convert `ListView.builder` to `GridView.builder` using `SliverGridDelegateWithMaxCrossAxisExtent` to automatically adjust column counts based on window size.
|
|
66
|
+
- [ ] **If optimizing a form or text block:** Wrap the component in a `ConstrainedBox`.
|
|
67
|
+
- [ ] Apply `BoxConstraints(maxWidth: [optimal_width])` to the `ConstrainedBox`.
|
|
68
|
+
- [ ] Wrap the `ConstrainedBox` in a `Center` widget to keep the constrained content centered on large screens.
|
|
69
|
+
- [ ] Run validator -> test on desktop/tablet target -> review horizontal stretching -> adjust `maxWidth` or grid extents.
|
|
70
|
+
|
|
71
|
+
## Examples
|
|
72
|
+
|
|
73
|
+
### Adaptive Layout using LayoutBuilder
|
|
74
|
+
Demonstrates switching between a mobile and desktop layout based on available width.
|
|
75
|
+
|
|
76
|
+
```dart
|
|
77
|
+
import 'package:flutter/material.dart';
|
|
78
|
+
|
|
79
|
+
const double largeScreenMinWidth = 600.0;
|
|
80
|
+
|
|
81
|
+
class AdaptiveLayout extends StatelessWidget {
|
|
82
|
+
const AdaptiveLayout({super.key});
|
|
83
|
+
|
|
84
|
+
@override
|
|
85
|
+
Widget build(BuildContext context) {
|
|
86
|
+
return LayoutBuilder(
|
|
87
|
+
builder: (context, constraints) {
|
|
88
|
+
if (constraints.maxWidth > largeScreenMinWidth) {
|
|
89
|
+
return _buildLargeScreenLayout();
|
|
90
|
+
} else {
|
|
91
|
+
return _buildSmallScreenLayout();
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
Widget _buildLargeScreenLayout() {
|
|
98
|
+
return Row(
|
|
99
|
+
children: [
|
|
100
|
+
const SizedBox(width: 250, child: Placeholder(color: Colors.blue)),
|
|
101
|
+
const VerticalDivider(width: 1),
|
|
102
|
+
Expanded(child: const Placeholder(color: Colors.green)),
|
|
103
|
+
],
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
Widget _buildSmallScreenLayout() {
|
|
108
|
+
return const Placeholder(color: Colors.green);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Constraining Width on Large Screens
|
|
114
|
+
Demonstrates preventing a widget from consuming all horizontal space.
|
|
115
|
+
|
|
116
|
+
```dart
|
|
117
|
+
import 'package:flutter/material.dart';
|
|
118
|
+
|
|
119
|
+
class ConstrainedContent extends StatelessWidget {
|
|
120
|
+
const ConstrainedContent({super.key});
|
|
121
|
+
|
|
122
|
+
@override
|
|
123
|
+
Widget build(BuildContext context) {
|
|
124
|
+
return Scaffold(
|
|
125
|
+
body: Center(
|
|
126
|
+
child: ConstrainedBox(
|
|
127
|
+
constraints: const BoxConstraints(
|
|
128
|
+
maxWidth: 800.0, // Maximum width for readability
|
|
129
|
+
),
|
|
130
|
+
child: ListView.builder(
|
|
131
|
+
itemCount: 50,
|
|
132
|
+
itemBuilder: (context, index) {
|
|
133
|
+
return ListTile(
|
|
134
|
+
title: Text('Item $index'),
|
|
135
|
+
);
|
|
136
|
+
},
|
|
137
|
+
),
|
|
138
|
+
),
|
|
139
|
+
),
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|