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,125 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: flutter-building-forms
|
|
3
|
+
description: Builds Flutter forms with validation and user input handling. Use when creating login screens, data entry forms, or any multi-field user input.
|
|
4
|
+
metadata:
|
|
5
|
+
model: models/gemini-3.1-pro-preview
|
|
6
|
+
last_modified: Thu, 12 Mar 2026 22:15:24 GMT
|
|
7
|
+
routing: manual
|
|
8
|
+
stacks: [flutter]
|
|
9
|
+
source: https://github.com/flutter/skills
|
|
10
|
+
license: BSD-3-Clause
|
|
11
|
+
---
|
|
12
|
+
# Building Validated Forms
|
|
13
|
+
|
|
14
|
+
## Contents
|
|
15
|
+
- [Form Architecture](#form-architecture)
|
|
16
|
+
- [Field Validation](#field-validation)
|
|
17
|
+
- [Workflow: Implementing a Validated Form](#workflow-implementing-a-validated-form)
|
|
18
|
+
- [Examples](#examples)
|
|
19
|
+
|
|
20
|
+
## Form Architecture
|
|
21
|
+
|
|
22
|
+
Implement forms using a `Form` widget to group and validate multiple input fields together.
|
|
23
|
+
|
|
24
|
+
- **Use a StatefulWidget:** Always host your `Form` inside a `StatefulWidget`.
|
|
25
|
+
- **Persist the GlobalKey:** Instantiate a `GlobalKey<FormState>` exactly once as a final variable within the `State` class. Do not generate a new `GlobalKey` inside the `build` method; doing so is resource-expensive and destroys the form's state on every rebuild.
|
|
26
|
+
- **Bind the Key:** Pass the `GlobalKey<FormState>` to the `key` property of the `Form` widget. This uniquely identifies the form and provides access to the `FormState` for validation and submission.
|
|
27
|
+
- **Alternative Access:** If dealing with highly complex widget trees where passing the key is impractical, use `Form.of(context)` to access the `FormState` from a descendant widget.
|
|
28
|
+
|
|
29
|
+
## Field Validation
|
|
30
|
+
|
|
31
|
+
Use `TextFormField` to render Material Design text inputs with built-in validation support. `TextFormField` is a convenience widget that automatically wraps a standard `TextField` inside a `FormField`.
|
|
32
|
+
|
|
33
|
+
- **Implement the Validator:** Provide a `validator()` callback function to each `TextFormField`.
|
|
34
|
+
- **Return Error Messages:** If the user's input is invalid, return a `String` containing the specific error message. The `Form` will automatically rebuild to display this text below the field.
|
|
35
|
+
- **Return Null for Success:** If the input passes validation, you must return `null`.
|
|
36
|
+
|
|
37
|
+
## Workflow: Implementing a Validated Form
|
|
38
|
+
|
|
39
|
+
Follow this sequential workflow to implement and validate a form. Copy the checklist to track your progress.
|
|
40
|
+
|
|
41
|
+
**Task Progress:**
|
|
42
|
+
- [ ] 1. Create a `StatefulWidget` and its corresponding `State` class.
|
|
43
|
+
- [ ] 2. Instantiate `final _formKey = GlobalKey<FormState>();` in the `State` class.
|
|
44
|
+
- [ ] 3. Return a `Form` widget in the `build` method and assign `key: _formKey`.
|
|
45
|
+
- [ ] 4. Add `TextFormField` widgets as descendants of the `Form`.
|
|
46
|
+
- [ ] 5. Write a `validator` function for each `TextFormField` (return `String` on error, `null` on success).
|
|
47
|
+
- [ ] 6. Add a submit button (e.g., `ElevatedButton`).
|
|
48
|
+
- [ ] 7. Implement the validation check in the button's `onPressed` callback using `_formKey.currentState!.validate()`.
|
|
49
|
+
|
|
50
|
+
### Validation Decision Logic
|
|
51
|
+
|
|
52
|
+
When the user triggers the submit action, execute the following conditional logic:
|
|
53
|
+
|
|
54
|
+
1. Call `_formKey.currentState!.validate()`.
|
|
55
|
+
2. **If `true` (Valid):** All validators returned `null`. Proceed with form submission (e.g., save data, make API call) and display a success indicator (e.g., a `SnackBar`).
|
|
56
|
+
3. **If `false` (Invalid):** One or more validators returned an error string. The `FormState` automatically rebuilds the UI to display the error messages.
|
|
57
|
+
4. **Feedback Loop:** Run validator -> review errors -> fix. The user must adjust their input and resubmit until `validate()` returns `true`.
|
|
58
|
+
|
|
59
|
+
## Examples
|
|
60
|
+
|
|
61
|
+
### Complete Validated Form Implementation
|
|
62
|
+
|
|
63
|
+
Use the following pattern to implement a robust, validated form.
|
|
64
|
+
|
|
65
|
+
```dart
|
|
66
|
+
import 'package:flutter/material.dart';
|
|
67
|
+
|
|
68
|
+
class UserRegistrationForm extends StatefulWidget {
|
|
69
|
+
const UserRegistrationForm({super.key});
|
|
70
|
+
|
|
71
|
+
@override
|
|
72
|
+
State<UserRegistrationForm> createState() => _UserRegistrationFormState();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
class _UserRegistrationFormState extends State<UserRegistrationForm> {
|
|
76
|
+
// 1. Persist the GlobalKey in the State class
|
|
77
|
+
final _formKey = GlobalKey<FormState>();
|
|
78
|
+
|
|
79
|
+
@override
|
|
80
|
+
Widget build(BuildContext context) {
|
|
81
|
+
// 2. Bind the key to the Form
|
|
82
|
+
return Form(
|
|
83
|
+
key: _formKey,
|
|
84
|
+
child: Column(
|
|
85
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
86
|
+
children: [
|
|
87
|
+
// 3. Add TextFormFields with validators
|
|
88
|
+
TextFormField(
|
|
89
|
+
decoration: const InputDecoration(
|
|
90
|
+
labelText: 'Username',
|
|
91
|
+
hintText: 'Enter your username',
|
|
92
|
+
),
|
|
93
|
+
validator: (value) {
|
|
94
|
+
if (value == null || value.isEmpty) {
|
|
95
|
+
return 'Please enter a username'; // Error state
|
|
96
|
+
}
|
|
97
|
+
if (value.length < 4) {
|
|
98
|
+
return 'Username must be at least 4 characters'; // Error state
|
|
99
|
+
}
|
|
100
|
+
return null; // Valid state
|
|
101
|
+
},
|
|
102
|
+
),
|
|
103
|
+
const SizedBox(height: 16),
|
|
104
|
+
// 4. Add the submit button
|
|
105
|
+
ElevatedButton(
|
|
106
|
+
onPressed: () {
|
|
107
|
+
// 5. Trigger validation logic
|
|
108
|
+
if (_formKey.currentState!.validate()) {
|
|
109
|
+
// Form is valid: Process data
|
|
110
|
+
ScaffoldMessenger.of(context).showSnackBar(
|
|
111
|
+
const SnackBar(content: Text('Processing Data')),
|
|
112
|
+
);
|
|
113
|
+
} else {
|
|
114
|
+
// Form is invalid: Errors are automatically displayed
|
|
115
|
+
debugPrint('Form validation failed.');
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
child: const Text('Submit'),
|
|
119
|
+
),
|
|
120
|
+
],
|
|
121
|
+
),
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: flutter-building-layouts
|
|
3
|
+
description: Builds Flutter layouts using the constraint system and layout widgets. Use when creating or refining the UI structure of a Flutter application.
|
|
4
|
+
metadata:
|
|
5
|
+
model: models/gemini-3.1-pro-preview
|
|
6
|
+
last_modified: Thu, 12 Mar 2026 22:14:15 GMT
|
|
7
|
+
routing: manual
|
|
8
|
+
stacks: [flutter]
|
|
9
|
+
source: https://github.com/flutter/skills
|
|
10
|
+
license: BSD-3-Clause
|
|
11
|
+
---
|
|
12
|
+
# Architecting Flutter Layouts
|
|
13
|
+
|
|
14
|
+
## Contents
|
|
15
|
+
- [Core Layout Principles](#core-layout-principles)
|
|
16
|
+
- [Structural Widgets](#structural-widgets)
|
|
17
|
+
- [Adaptive and Responsive Design](#adaptive-and-responsive-design)
|
|
18
|
+
- [Workflow: Implementing a Complex Layout](#workflow-implementing-a-complex-layout)
|
|
19
|
+
- [Examples](#examples)
|
|
20
|
+
|
|
21
|
+
## Core Layout Principles
|
|
22
|
+
|
|
23
|
+
Master the fundamental Flutter layout rule: **Constraints go down. Sizes go up. Parent sets position.**
|
|
24
|
+
|
|
25
|
+
* **Pass Constraints Down:** Always pass constraints (minimum/maximum width and height) from the parent Widget to its children. A Widget cannot choose its own size independently of its parent's constraints.
|
|
26
|
+
* **Pass Sizes Up:** Calculate the child Widget's desired size within the given constraints and pass this size back up to the parent.
|
|
27
|
+
* **Set Position via Parent:** Define the `x` and `y` coordinates of a child Widget exclusively within the parent Widget. Children do not know their own position on the screen.
|
|
28
|
+
* **Avoid Unbounded Constraints:** Never pass unbounded constraints (e.g., `double.infinity`) in the cross-axis of a flex box (`Row` or `Column`) or within scrollable regions (`ListView`). This causes render exceptions.
|
|
29
|
+
|
|
30
|
+
## Structural Widgets
|
|
31
|
+
|
|
32
|
+
Select the appropriate structural Widget based on the required spatial arrangement.
|
|
33
|
+
|
|
34
|
+
* **Use `Row` and `Column`:** Implement `Row` for horizontal linear layouts and `Column` for vertical linear layouts. Control child alignment using `mainAxisAlignment` and `crossAxisAlignment`.
|
|
35
|
+
* **Use `Expanded` and `Flexible`:** Wrap children of `Row` or `Column` in `Expanded` to force them to fill available space, or `Flexible` to allow them to size themselves up to the available space.
|
|
36
|
+
* **Use `Container`:** Wrap Widgets in a `Container` when you need to apply padding, margins, borders, or background colors.
|
|
37
|
+
* **Use `Stack`:** Implement `Stack` when Widgets must overlap on the Z-axis. Use `Positioned` to anchor children to specific edges of the `Stack`.
|
|
38
|
+
* **Use `SizedBox`:** Enforce strict, tight constraints on a child Widget by wrapping it in a `SizedBox` with explicit `width` and `height` values.
|
|
39
|
+
|
|
40
|
+
## Adaptive and Responsive Design
|
|
41
|
+
|
|
42
|
+
Apply conditional logic to handle varying screen sizes and form factors.
|
|
43
|
+
|
|
44
|
+
* **If fitting UI into available space (Responsive):** Use `LayoutBuilder`, `Expanded`, and `Flexible` to dynamically adjust the size and placement of elements based on the parent's constraints.
|
|
45
|
+
* **If adjusting UI usability for a specific form factor (Adaptive):** Use conditional rendering to swap entire layout structures. For example, render a bottom navigation bar on mobile, but a side navigation rail on tablets/desktop.
|
|
46
|
+
|
|
47
|
+
## Workflow: Implementing a Complex Layout
|
|
48
|
+
|
|
49
|
+
Follow this sequential workflow to architect and implement robust Flutter layouts.
|
|
50
|
+
|
|
51
|
+
### Task Progress
|
|
52
|
+
- [ ] **Phase 1: Visual Deconstruction**
|
|
53
|
+
- [ ] Break down the target UI into a hierarchy of rows, columns, and grids.
|
|
54
|
+
- [ ] Identify overlapping elements (requiring `Stack`).
|
|
55
|
+
- [ ] Identify scrolling regions (requiring `ListView` or `SingleChildScrollView`).
|
|
56
|
+
- [ ] **Phase 2: Constraint Planning**
|
|
57
|
+
- [ ] Determine which Widgets require tight constraints (fixed size) vs. loose constraints (flexible size).
|
|
58
|
+
- [ ] Identify potential unbounded constraint risks (e.g., a `ListView` inside a `Column`).
|
|
59
|
+
- [ ] **Phase 3: Implementation**
|
|
60
|
+
- [ ] Build the layout from the outside in, starting with the `Scaffold` and primary structural Widgets.
|
|
61
|
+
- [ ] Extract deeply nested layout sections into separate, stateless Widgets to maintain readability.
|
|
62
|
+
- [ ] **Phase 4: Validation and Feedback Loop**
|
|
63
|
+
- [ ] Run the application on target devices/simulators.
|
|
64
|
+
- [ ] **Run validator -> review errors -> fix:** Open the Flutter Inspector. Enable "Debug Paint" to visualize render boxes.
|
|
65
|
+
- [ ] Check for yellow/black striped overflow warnings.
|
|
66
|
+
- [ ] If overflow occurs: Wrap the overflowing Widget in `Expanded` (if inside a flex box) or wrap the parent in a scrollable Widget.
|
|
67
|
+
|
|
68
|
+
## Examples
|
|
69
|
+
|
|
70
|
+
### Example: Resolving Unbounded Constraints in Flex Boxes
|
|
71
|
+
|
|
72
|
+
**Anti-pattern:** Placing a `ListView` directly inside a `Column` causes an unbounded height exception because the `Column` provides infinite vertical space to the `ListView`.
|
|
73
|
+
|
|
74
|
+
```dart
|
|
75
|
+
// BAD: Throws unbounded height exception
|
|
76
|
+
Column(
|
|
77
|
+
children: [
|
|
78
|
+
Text('Header'),
|
|
79
|
+
ListView(
|
|
80
|
+
children: [/* items */],
|
|
81
|
+
),
|
|
82
|
+
],
|
|
83
|
+
)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Implementation:** Wrap the `ListView` in an `Expanded` Widget to bound its height to the remaining space in the `Column`.
|
|
87
|
+
|
|
88
|
+
```dart
|
|
89
|
+
// GOOD: ListView is constrained to remaining space
|
|
90
|
+
Column(
|
|
91
|
+
children: [
|
|
92
|
+
Text('Header'),
|
|
93
|
+
Expanded(
|
|
94
|
+
child: ListView(
|
|
95
|
+
children: [/* items */],
|
|
96
|
+
),
|
|
97
|
+
),
|
|
98
|
+
],
|
|
99
|
+
)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Example: Responsive Layout with LayoutBuilder
|
|
103
|
+
|
|
104
|
+
Implement `LayoutBuilder` to conditionally render different structural Widgets based on available width.
|
|
105
|
+
|
|
106
|
+
```dart
|
|
107
|
+
Widget buildAdaptiveLayout(BuildContext context) {
|
|
108
|
+
return LayoutBuilder(
|
|
109
|
+
builder: (context, constraints) {
|
|
110
|
+
// Conditional logic based on screen width
|
|
111
|
+
if (constraints.maxWidth > 600) {
|
|
112
|
+
// Tablet/Desktop: Side-by-side layout
|
|
113
|
+
return Row(
|
|
114
|
+
children: [
|
|
115
|
+
SizedBox(width: 250, child: SidebarWidget()),
|
|
116
|
+
Expanded(child: MainContentWidget()),
|
|
117
|
+
],
|
|
118
|
+
);
|
|
119
|
+
} else {
|
|
120
|
+
// Mobile: Stacked layout with navigation
|
|
121
|
+
return Column(
|
|
122
|
+
children: [
|
|
123
|
+
Expanded(child: MainContentWidget()),
|
|
124
|
+
BottomNavigationBarWidget(),
|
|
125
|
+
],
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
```
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: flutter-building-plugins
|
|
3
|
+
description: Builds Flutter plugins that provide native interop for other apps to use. Use when creating reusable packages that bridge Flutter with platform-specific functionality.
|
|
4
|
+
metadata:
|
|
5
|
+
model: models/gemini-3.1-pro-preview
|
|
6
|
+
last_modified: Thu, 12 Mar 2026 22:21:35 GMT
|
|
7
|
+
routing: manual
|
|
8
|
+
stacks: [flutter]
|
|
9
|
+
source: https://github.com/flutter/skills
|
|
10
|
+
license: BSD-3-Clause
|
|
11
|
+
---
|
|
12
|
+
# Developing Flutter Plugins
|
|
13
|
+
|
|
14
|
+
## Contents
|
|
15
|
+
- [Architecture & Design Patterns](#architecture--design-patterns)
|
|
16
|
+
- [Workflow: Creating a New Plugin](#workflow-creating-a-new-plugin)
|
|
17
|
+
- [Workflow: Implementing Android Platform Code](#workflow-implementing-android-platform-code)
|
|
18
|
+
- [Workflow: Implementing Windows Platform Code](#workflow-implementing-windows-platform-code)
|
|
19
|
+
- [Workflow: Adding Platforms to an Existing Plugin](#workflow-adding-platforms-to-an-existing-plugin)
|
|
20
|
+
- [Examples](#examples)
|
|
21
|
+
|
|
22
|
+
## Architecture & Design Patterns
|
|
23
|
+
|
|
24
|
+
### Federated Plugins
|
|
25
|
+
Implement federated plugins to split a plugin's API across multiple packages, allowing independent teams to build platform-specific implementations. Structure federated plugins into three distinct components:
|
|
26
|
+
1. **App-facing interface:** The primary package users depend on. It exports the public API.
|
|
27
|
+
2. **Platform interface:** The package defining the common interface that all platform implementations must implement.
|
|
28
|
+
3. **Platform implementations:** Independent packages containing platform-specific code (e.g., `my_plugin_android`, `my_plugin_windows`).
|
|
29
|
+
|
|
30
|
+
### FFI vs. Standard Plugins
|
|
31
|
+
Choose the correct plugin template based on your native interoperability requirements:
|
|
32
|
+
* **Standard Plugins (`--template=plugin`):** Use for accessing platform-specific APIs (e.g., Android SDK, iOS frameworks) via Method Channels.
|
|
33
|
+
* **FFI Plugins (`--template=plugin_ffi`):** Use for accessing C/C++ native libraries, configuring Google Play services on Android, or using static linking on iOS/macOS.
|
|
34
|
+
* *Constraint:* FFI plugin packages support bundling native code and method channel registration code, but *not* method channels themselves. If you require both method channels and FFI, use the standard non-FFI plugin template.
|
|
35
|
+
|
|
36
|
+
## Workflow: Creating a New Plugin
|
|
37
|
+
|
|
38
|
+
Follow this workflow to initialize a new plugin package.
|
|
39
|
+
|
|
40
|
+
**Task Progress:**
|
|
41
|
+
- [ ] Determine if the plugin requires FFI or standard Method Channels.
|
|
42
|
+
- [ ] Execute the appropriate `flutter create` command.
|
|
43
|
+
- [ ] Verify the generated directory structure.
|
|
44
|
+
|
|
45
|
+
**Conditional Initialization:**
|
|
46
|
+
* **If creating a STANDARD plugin:**
|
|
47
|
+
Run the following command, specifying your supported platforms, organization, and preferred languages (defaults are Swift and Kotlin):
|
|
48
|
+
```bash
|
|
49
|
+
flutter create --template=plugin \
|
|
50
|
+
--platforms=android,ios,web,linux,macos,windows \
|
|
51
|
+
--org com.example.organization \
|
|
52
|
+
-i objc -a java \
|
|
53
|
+
my_plugin
|
|
54
|
+
```
|
|
55
|
+
* **If creating an FFI plugin:**
|
|
56
|
+
Run the following command to generate a project with Dart code in `lib` (using `dart:ffi`) and native source code in `src` (with a `CMakeLists.txt`):
|
|
57
|
+
```bash
|
|
58
|
+
flutter create --template=plugin_ffi my_ffi_plugin
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Workflow: Implementing Android Platform Code
|
|
62
|
+
|
|
63
|
+
Always edit Android platform code using Android Studio to ensure proper code completion and Gradle synchronization.
|
|
64
|
+
|
|
65
|
+
**Task Progress:**
|
|
66
|
+
- [ ] Run initial build to generate necessary Gradle files.
|
|
67
|
+
- [ ] Open the Android module in Android Studio.
|
|
68
|
+
- [ ] Implement `FlutterPlugin` and lifecycle-aware interfaces.
|
|
69
|
+
- [ ] Refactor legacy `registerWith` logic.
|
|
70
|
+
- [ ] Run validator -> review errors -> fix.
|
|
71
|
+
|
|
72
|
+
1. **Generate Build Files:**
|
|
73
|
+
Build the code at least once before editing to resolve dependencies.
|
|
74
|
+
```bash
|
|
75
|
+
cd example
|
|
76
|
+
flutter build apk --config-only
|
|
77
|
+
```
|
|
78
|
+
2. **Open in IDE:**
|
|
79
|
+
Launch Android Studio and open the `example/android/build.gradle` or `example/android/build.gradle.kts` file.
|
|
80
|
+
3. **Locate Source:**
|
|
81
|
+
Navigate to your plugin's source code at `java/<organization-path>/<PluginName>`.
|
|
82
|
+
4. **Implement V2 Embedding:**
|
|
83
|
+
* Implement the `FlutterPlugin` interface.
|
|
84
|
+
* Ensure your plugin class has a public constructor.
|
|
85
|
+
* Extract shared initialization logic from the legacy `registerWith()` method and the new `onAttachedToEngine()` method into a single private method. Both entry points must call this private method to maintain backward compatibility without duplicating logic.
|
|
86
|
+
5. **Implement Lifecycle Interfaces:**
|
|
87
|
+
* **If your plugin requires an `Activity` reference:** Implement the `ActivityAware` interface and handle the `onAttachedToActivity`, `onDetachedFromActivityForConfigChanges`, `onReattachedToActivityForConfigChanges`, and `onDetachedFromActivity` callbacks.
|
|
88
|
+
* **If your plugin runs in a background `Service`:** Implement the `ServiceAware` interface.
|
|
89
|
+
6. **Update Example App:**
|
|
90
|
+
Ensure the example app's `MainActivity.java` extends the v2 embedding `io.flutter.embedding.android.FlutterActivity`.
|
|
91
|
+
7. **Document API:**
|
|
92
|
+
Document all non-overridden public members in your Android implementation.
|
|
93
|
+
|
|
94
|
+
## Workflow: Implementing Windows Platform Code
|
|
95
|
+
|
|
96
|
+
Always edit Windows platform code using Visual Studio.
|
|
97
|
+
|
|
98
|
+
**Task Progress:**
|
|
99
|
+
- [ ] Run initial build to generate the Visual Studio solution.
|
|
100
|
+
- [ ] Open the solution in Visual Studio.
|
|
101
|
+
- [ ] Implement C++ logic.
|
|
102
|
+
- [ ] Rebuild the solution.
|
|
103
|
+
|
|
104
|
+
1. **Generate Build Files:**
|
|
105
|
+
```bash
|
|
106
|
+
cd example
|
|
107
|
+
flutter build windows
|
|
108
|
+
```
|
|
109
|
+
2. **Open in IDE:**
|
|
110
|
+
Launch Visual Studio and open the `example/build/windows/hello_example.sln` file.
|
|
111
|
+
3. **Locate Source:**
|
|
112
|
+
Navigate to `hello_plugin/Source Files` and `hello_plugin/Header Files` in the Solution Explorer.
|
|
113
|
+
4. **Rebuild:**
|
|
114
|
+
After making changes to the C++ plugin code, you *must* rebuild the solution in Visual Studio before running the app, or the outdated plugin binary will be used.
|
|
115
|
+
|
|
116
|
+
## Workflow: Adding Platforms to an Existing Plugin
|
|
117
|
+
|
|
118
|
+
Use this workflow to retrofit an existing plugin with support for additional platforms.
|
|
119
|
+
|
|
120
|
+
**Task Progress:**
|
|
121
|
+
- [ ] Run the platform addition command.
|
|
122
|
+
- [ ] Update iOS/macOS podspecs (if applicable).
|
|
123
|
+
- [ ] Implement the platform-specific code.
|
|
124
|
+
|
|
125
|
+
1. **Run Create Command:**
|
|
126
|
+
Navigate to the root directory of your existing plugin and run:
|
|
127
|
+
```bash
|
|
128
|
+
flutter create --template=plugin --platforms=web,macos .
|
|
129
|
+
```
|
|
130
|
+
2. **Update Podspecs:**
|
|
131
|
+
If adding iOS or macOS support, open the generated `.podspec` file and configure the required dependencies and deployment targets.
|
|
132
|
+
|
|
133
|
+
## Examples
|
|
134
|
+
|
|
135
|
+
### Android V2 Embedding Implementation
|
|
136
|
+
High-fidelity example of an Android plugin implementing `FlutterPlugin` and `ActivityAware` while maintaining legacy compatibility.
|
|
137
|
+
|
|
138
|
+
```java
|
|
139
|
+
package com.example.myplugin;
|
|
140
|
+
|
|
141
|
+
import androidx.annotation.NonNull;
|
|
142
|
+
import io.flutter.embedding.engine.plugins.FlutterPlugin;
|
|
143
|
+
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
|
|
144
|
+
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
|
|
145
|
+
import io.flutter.plugin.common.MethodCall;
|
|
146
|
+
import io.flutter.plugin.common.MethodChannel;
|
|
147
|
+
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
|
|
148
|
+
import io.flutter.plugin.common.MethodChannel.Result;
|
|
149
|
+
import io.flutter.plugin.common.PluginRegistry.Registrar;
|
|
150
|
+
|
|
151
|
+
/** MyPlugin */
|
|
152
|
+
public class MyPlugin implements FlutterPlugin, MethodCallHandler, ActivityAware {
|
|
153
|
+
private MethodChannel channel;
|
|
154
|
+
|
|
155
|
+
// Public constructor required for v2 embedding
|
|
156
|
+
public MyPlugin() {}
|
|
157
|
+
|
|
158
|
+
@Override
|
|
159
|
+
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
|
|
160
|
+
setupChannel(flutterPluginBinding.getBinaryMessenger());
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Legacy v1 embedding support
|
|
164
|
+
public static void registerWith(Registrar registrar) {
|
|
165
|
+
MyPlugin plugin = new MyPlugin();
|
|
166
|
+
plugin.setupChannel(registrar.messenger());
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Shared initialization logic
|
|
170
|
+
private void setupChannel(BinaryMessenger messenger) {
|
|
171
|
+
channel = new MethodChannel(messenger, "my_plugin");
|
|
172
|
+
channel.setMethodCallHandler(this);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
@Override
|
|
176
|
+
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
|
|
177
|
+
if (call.method.equals("getPlatformVersion")) {
|
|
178
|
+
result.success("Android " + android.os.Build.VERSION.RELEASE);
|
|
179
|
+
} else {
|
|
180
|
+
result.notImplemented();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
@Override
|
|
185
|
+
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
|
|
186
|
+
channel.setMethodCallHandler(null);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
@Override
|
|
190
|
+
public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
|
|
191
|
+
// Handle Activity attachment
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
@Override
|
|
195
|
+
public void onDetachedFromActivityForConfigChanges() {
|
|
196
|
+
// Handle config changes
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
@Override
|
|
200
|
+
public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) {
|
|
201
|
+
// Handle reattachment
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
@Override
|
|
205
|
+
public void onDetachedFromActivity() {
|
|
206
|
+
// Clean up Activity references
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
```
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: flutter-caching-data
|
|
3
|
+
description: Implements caching strategies for Flutter apps to improve performance and offline support. Use when retaining app data locally to reduce network requests or speed up startup.
|
|
4
|
+
metadata:
|
|
5
|
+
model: models/gemini-3.1-pro-preview
|
|
6
|
+
last_modified: Thu, 12 Mar 2026 22:19:54 GMT
|
|
7
|
+
routing: manual
|
|
8
|
+
stacks: [flutter]
|
|
9
|
+
source: https://github.com/flutter/skills
|
|
10
|
+
license: BSD-3-Clause
|
|
11
|
+
---
|
|
12
|
+
# Implementing Flutter Caching and Offline-First Architectures
|
|
13
|
+
|
|
14
|
+
## Contents
|
|
15
|
+
- [Selecting a Caching Strategy](#selecting-a-caching-strategy)
|
|
16
|
+
- [Implementing Offline-First Data Synchronization](#implementing-offline-first-data-synchronization)
|
|
17
|
+
- [Managing File System and SQLite Persistence](#managing-file-system-and-sqlite-persistence)
|
|
18
|
+
- [Optimizing UI, Scroll, and Image Caching](#optimizing-ui-scroll-and-image-caching)
|
|
19
|
+
- [Caching the FlutterEngine (Android)](#caching-the-flutterengine-android)
|
|
20
|
+
- [Workflows](#workflows)
|
|
21
|
+
|
|
22
|
+
## Selecting a Caching Strategy
|
|
23
|
+
|
|
24
|
+
Apply the appropriate caching mechanism based on the data lifecycle and size requirements.
|
|
25
|
+
|
|
26
|
+
* **If storing small, non-critical UI states or preferences:** Use `shared_preferences`.
|
|
27
|
+
* **If storing large, structured datasets:** Use on-device databases (SQLite via `sqflite`, Drift, Hive CE, or Isar).
|
|
28
|
+
* **If storing binary data or large media:** Use file system caching via `path_provider`.
|
|
29
|
+
* **If retaining user session state (navigation, scroll positions):** Implement Flutter's built-in state restoration to sync the Element tree with the engine.
|
|
30
|
+
* **If optimizing Android initialization:** Pre-warm and cache the `FlutterEngine`.
|
|
31
|
+
|
|
32
|
+
## Implementing Offline-First Data Synchronization
|
|
33
|
+
|
|
34
|
+
Design repositories as the single source of truth, combining local databases and remote API clients.
|
|
35
|
+
|
|
36
|
+
### Read Operations (Stream Approach)
|
|
37
|
+
Yield local data immediately for fast UI rendering, then fetch remote data, update the local cache, and yield the fresh data.
|
|
38
|
+
|
|
39
|
+
```dart
|
|
40
|
+
Stream<UserProfile> getUserProfile() async* {
|
|
41
|
+
// 1. Yield local cache first
|
|
42
|
+
final localProfile = await _databaseService.fetchUserProfile();
|
|
43
|
+
if (localProfile != null) yield localProfile;
|
|
44
|
+
|
|
45
|
+
// 2. Fetch remote, update cache, yield fresh data
|
|
46
|
+
try {
|
|
47
|
+
final remoteProfile = await _apiClientService.getUserProfile();
|
|
48
|
+
await _databaseService.updateUserProfile(remoteProfile);
|
|
49
|
+
yield remoteProfile;
|
|
50
|
+
} catch (e) {
|
|
51
|
+
// Handle network failure; UI already has local data
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Write Operations
|
|
57
|
+
Determine the write strategy based on data criticality:
|
|
58
|
+
* **If strict server synchronization is required (Online-only):** Attempt the API call first. Only update the local database if the API call succeeds.
|
|
59
|
+
* **If offline availability is prioritized (Offline-first):** Write to the local database immediately. Attempt the API call. If the API call fails, flag the local record for background synchronization.
|
|
60
|
+
|
|
61
|
+
### Background Synchronization
|
|
62
|
+
Add a `synchronized` boolean flag to your data models. Run a periodic background task (e.g., via `workmanager` or a `Timer`) to push unsynchronized local changes to the server.
|
|
63
|
+
|
|
64
|
+
## Managing File System and SQLite Persistence
|
|
65
|
+
|
|
66
|
+
### File System Caching
|
|
67
|
+
Use `path_provider` to locate the correct directory.
|
|
68
|
+
* Use `getApplicationDocumentsDirectory()` for persistent data.
|
|
69
|
+
* Use `getTemporaryDirectory()` for cache data the OS can clear.
|
|
70
|
+
|
|
71
|
+
```dart
|
|
72
|
+
Future<File> get _localFile async {
|
|
73
|
+
final directory = await getApplicationDocumentsDirectory();
|
|
74
|
+
return File('${directory.path}/cache.txt');
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### SQLite Persistence
|
|
79
|
+
Use `sqflite` for relational data caching. Always use `whereArgs` to prevent SQL injection.
|
|
80
|
+
|
|
81
|
+
```dart
|
|
82
|
+
Future<void> updateCachedRecord(Record record) async {
|
|
83
|
+
final db = await database;
|
|
84
|
+
await db.update(
|
|
85
|
+
'records',
|
|
86
|
+
record.toMap(),
|
|
87
|
+
where: 'id = ?',
|
|
88
|
+
whereArgs: [record.id], // NEVER use string interpolation here
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Optimizing UI, Scroll, and Image Caching
|
|
94
|
+
|
|
95
|
+
### Image Caching
|
|
96
|
+
Image I/O and decompression are expensive.
|
|
97
|
+
* Use the `cached_network_image` package to handle file-system caching of remote images.
|
|
98
|
+
* **Custom ImageProviders:** If implementing a custom `ImageProvider`, override `createStream()` and `resolveStreamForKey()` instead of the deprecated `resolve()` method.
|
|
99
|
+
* **Cache Sizing:** The `ImageCache.maxByteSize` no longer automatically expands for large images. If loading images larger than the default cache size, manually increase `ImageCache.maxByteSize` or subclass `ImageCache` to implement custom eviction logic.
|
|
100
|
+
|
|
101
|
+
### Scroll Caching
|
|
102
|
+
When configuring caching for scrollable widgets (`ListView`, `GridView`, `Viewport`), use the `scrollCacheExtent` property with a `ScrollCacheExtent` object. Do not use the deprecated `cacheExtent` and `cacheExtentStyle` properties.
|
|
103
|
+
|
|
104
|
+
```dart
|
|
105
|
+
// Correct implementation
|
|
106
|
+
ListView(
|
|
107
|
+
scrollCacheExtent: const ScrollCacheExtent.pixels(500.0),
|
|
108
|
+
children: // ...
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
Viewport(
|
|
112
|
+
scrollCacheExtent: const ScrollCacheExtent.viewport(0.5),
|
|
113
|
+
slivers: // ...
|
|
114
|
+
)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Widget Caching
|
|
118
|
+
* Avoid overriding `operator ==` on `Widget` objects. It causes O(N²) behavior during rebuilds.
|
|
119
|
+
* **Exception:** You may override `operator ==` *only* on leaf widgets (no children) where comparing properties is significantly faster than rebuilding, and the properties rarely change.
|
|
120
|
+
* Prefer using `const` constructors to allow the framework to short-circuit rebuilds automatically.
|
|
121
|
+
|
|
122
|
+
## Caching the FlutterEngine (Android)
|
|
123
|
+
|
|
124
|
+
To eliminate the non-trivial warm-up time of a `FlutterEngine` when adding Flutter to an existing Android app, pre-warm and cache the engine.
|
|
125
|
+
|
|
126
|
+
1. Instantiate and pre-warm the engine in the `Application` class.
|
|
127
|
+
2. Store it in the `FlutterEngineCache`.
|
|
128
|
+
3. Retrieve it using `withCachedEngine` in the `FlutterActivity` or `FlutterFragment`.
|
|
129
|
+
|
|
130
|
+
```kotlin
|
|
131
|
+
// 1. Pre-warm in Application class
|
|
132
|
+
val flutterEngine = FlutterEngine(this)
|
|
133
|
+
flutterEngine.navigationChannel.setInitialRoute("/cached_route")
|
|
134
|
+
flutterEngine.dartExecutor.executeDartEntrypoint(DartEntrypoint.createDefault())
|
|
135
|
+
|
|
136
|
+
// 2. Cache the engine
|
|
137
|
+
FlutterEngineCache.getInstance().put("my_engine_id", flutterEngine)
|
|
138
|
+
|
|
139
|
+
// 3. Use in Activity/Fragment
|
|
140
|
+
startActivity(
|
|
141
|
+
FlutterActivity.withCachedEngine("my_engine_id").build(this)
|
|
142
|
+
)
|
|
143
|
+
```
|
|
144
|
+
*Note: You cannot set an initial route via the Activity/Fragment builder when using a cached engine. Set the initial route on the engine's navigation channel before executing the Dart entrypoint.*
|
|
145
|
+
|
|
146
|
+
## Workflows
|
|
147
|
+
|
|
148
|
+
### Workflow: Implementing an Offline-First Repository
|
|
149
|
+
Follow these steps to implement a robust offline-first data layer.
|
|
150
|
+
|
|
151
|
+
- [ ] **Task Progress:**
|
|
152
|
+
- [ ] Define the data model with a `synchronized` boolean flag (default `false`).
|
|
153
|
+
- [ ] Implement the local `DatabaseService` (SQLite/Hive) with CRUD operations.
|
|
154
|
+
- [ ] Implement the remote `ApiClientService` for network requests.
|
|
155
|
+
- [ ] Create the `Repository` class combining both services.
|
|
156
|
+
- [ ] Implement the read method returning a `Stream<T>` (yield local, fetch remote, update local, yield remote).
|
|
157
|
+
- [ ] Implement the write method (write local, attempt remote, update `synchronized` flag).
|
|
158
|
+
- [ ] Implement a background sync function to process records where `synchronized == false`.
|
|
159
|
+
- [ ] Run validator -> review errors -> fix (Test offline behavior by disabling network).
|
|
160
|
+
|
|
161
|
+
### Workflow: Pre-warming the Android FlutterEngine
|
|
162
|
+
Follow these steps to cache the FlutterEngine for seamless Android integration.
|
|
163
|
+
|
|
164
|
+
- [ ] **Task Progress:**
|
|
165
|
+
- [ ] Locate the Android `Application` class (create one if it doesn't exist and register in `AndroidManifest.xml`).
|
|
166
|
+
- [ ] Instantiate a new `FlutterEngine`.
|
|
167
|
+
- [ ] (Optional) Set the initial route via `navigationChannel.setInitialRoute()`.
|
|
168
|
+
- [ ] Execute the Dart entrypoint via `dartExecutor.executeDartEntrypoint()`.
|
|
169
|
+
- [ ] Store the engine in `FlutterEngineCache.getInstance().put()`.
|
|
170
|
+
- [ ] Update the target `FlutterActivity` or `FlutterFragment` to use `.withCachedEngine("id")`.
|
|
171
|
+
- [ ] Run validator -> review errors -> fix (Verify no blank screen appears during transition).
|