From 130378c2e7f847749664a236a150465453f046b9 Mon Sep 17 00:00:00 2001 From: John Ryan Date: Tue, 21 Apr 2026 09:35:58 -0700 Subject: [PATCH 01/18] Remove all skills --- .../SKILL.md | 202 ----------------- skills/flutter-animating-apps/SKILL.md | 182 ---------------- skills/flutter-architecting-apps/SKILL.md | 163 -------------- skills/flutter-building-forms/SKILL.md | 122 ----------- skills/flutter-building-layouts/SKILL.md | 128 ----------- skills/flutter-building-plugins/SKILL.md | 206 ------------------ skills/flutter-caching-data/SKILL.md | 168 -------------- .../flutter-embedding-native-views/SKILL.md | 197 ----------------- skills/flutter-handling-concurrency/SKILL.md | 179 --------------- .../flutter-handling-http-and-json/SKILL.md | 181 --------------- .../SKILL.md | 205 ----------------- .../flutter-improving-accessibility/SKILL.md | 110 ---------- .../SKILL.md | 127 ----------- skills/flutter-localizing-apps/SKILL.md | 204 ----------------- skills/flutter-managing-state/SKILL.md | 203 ----------------- skills/flutter-reducing-app-size/SKILL.md | 100 --------- skills/flutter-setting-up-on-linux/SKILL.md | 138 ------------ skills/flutter-setting-up-on-macos/SKILL.md | 75 ------- skills/flutter-setting-up-on-windows/SKILL.md | 103 --------- skills/flutter-testing-apps/SKILL.md | 182 ---------------- skills/flutter-theming-apps/SKILL.md | 160 -------------- .../flutter-working-with-databases/SKILL.md | 200 ----------------- 22 files changed, 3535 deletions(-) delete mode 100644 skills/flutter-adding-home-screen-widgets/SKILL.md delete mode 100644 skills/flutter-animating-apps/SKILL.md delete mode 100644 skills/flutter-architecting-apps/SKILL.md delete mode 100644 skills/flutter-building-forms/SKILL.md delete mode 100644 skills/flutter-building-layouts/SKILL.md delete mode 100644 skills/flutter-building-plugins/SKILL.md delete mode 100644 skills/flutter-caching-data/SKILL.md delete mode 100644 skills/flutter-embedding-native-views/SKILL.md delete mode 100644 skills/flutter-handling-concurrency/SKILL.md delete mode 100644 skills/flutter-handling-http-and-json/SKILL.md delete mode 100644 skills/flutter-implementing-navigation-and-routing/SKILL.md delete mode 100644 skills/flutter-improving-accessibility/SKILL.md delete mode 100644 skills/flutter-interoperating-with-native-apis/SKILL.md delete mode 100644 skills/flutter-localizing-apps/SKILL.md delete mode 100644 skills/flutter-managing-state/SKILL.md delete mode 100644 skills/flutter-reducing-app-size/SKILL.md delete mode 100644 skills/flutter-setting-up-on-linux/SKILL.md delete mode 100644 skills/flutter-setting-up-on-macos/SKILL.md delete mode 100644 skills/flutter-setting-up-on-windows/SKILL.md delete mode 100644 skills/flutter-testing-apps/SKILL.md delete mode 100644 skills/flutter-theming-apps/SKILL.md delete mode 100644 skills/flutter-working-with-databases/SKILL.md diff --git a/skills/flutter-adding-home-screen-widgets/SKILL.md b/skills/flutter-adding-home-screen-widgets/SKILL.md deleted file mode 100644 index de983ee..0000000 --- a/skills/flutter-adding-home-screen-widgets/SKILL.md +++ /dev/null @@ -1,202 +0,0 @@ ---- -name: flutter-adding-home-screen-widgets -description: Adds home screen widgets to a Flutter app for Android and iOS. Use when providing glanceable app information or quick actions on the device home screen. -metadata: - model: models/gemini-3.1-pro-preview - last_modified: Thu, 12 Mar 2026 22:23:50 GMT - ---- -# Implementing Flutter Home Screen Widgets - -## Contents -- [Architecture & Data Flow](#architecture--data-flow) -- [Flutter Integration Workflow](#flutter-integration-workflow) -- [iOS Implementation Workflow](#ios-implementation-workflow) -- [Android Implementation Workflow](#android-implementation-workflow) -- [Advanced Techniques](#advanced-techniques) -- [Examples](#examples) - -## Architecture & Data Flow -Home Screen Widgets require native UI implementation (SwiftUI for iOS, XML/Kotlin for Android). The Flutter app communicates with these native widgets via shared local storage (`UserDefaults` on iOS, `SharedPreferences` on Android) using the `home_widget` package. - -- **Data Write:** Flutter app writes key-value pairs or renders images to a shared container. -- **Trigger:** Flutter app signals the native OS to update the widget. -- **Data Read:** Native widget wakes up, reads the key-value pairs or images from the shared container, and updates its UI. - -## Flutter Integration Workflow - -Use this checklist to implement the Dart side of the Home Screen Widget integration. - -- [ ] **Step 1: Initialize the App Group.** Call `HomeWidget.setAppGroupId('')` in `initState()` or app startup. -- [ ] **Step 2: Save Data.** Use `HomeWidget.saveWidgetData('key', value)` to write data to shared storage. -- [ ] **Step 3: Trigger Update.** Call `HomeWidget.updateWidget(iOSName: 'YourIOSWidget', androidName: 'YourAndroidWidget')` to notify the OS. -- [ ] **Step 4: Validate.** Run Flutter build -> review console for missing plugin registrations -> fix. - -## iOS Implementation Workflow - -If targeting iOS, implement the widget using Xcode and SwiftUI. - -- [ ] **Step 1: Create Target.** Open `ios/Runner.xcworkspace` in Xcode. Add a new **Widget Extension** target. Disable "Include Live Activity" and "Include Configuration Intent" unless explicitly required. -- [ ] **Step 2: Configure App Groups.** Add the **App Groups** capability to *both* the Runner target and the Widget Extension target. Ensure the App Group ID matches the one used in Dart. -- [ ] **Step 3: Define TimelineEntry.** Create a struct conforming to `TimelineEntry` to hold the data passed from shared storage. -- [ ] **Step 4: Implement TimelineProvider.** - - In `getSnapshot` and `getTimeline`, instantiate `UserDefaults(suiteName: "")`. - - Extract values using `userDefaults?.string(forKey: "your_key")`. - - Return the populated `TimelineEntry`. -- [ ] **Step 5: Build UI.** Implement the SwiftUI `View` to display the data from the `TimelineEntry`. -- [ ] **Step 6: Validate.** Run Xcode build for the Widget Extension -> review provisioning/App Group errors -> fix. - -## Android Implementation Workflow - -If targeting Android, implement the widget using Android Studio and XML/Kotlin. - -- [ ] **Step 1: Create App Widget.** Open the `android` folder in Android Studio. Right-click the app directory -> **New -> Widget -> App Widget**. -- [ ] **Step 2: Define Layout.** Edit `res/layout/.xml` to define the UI using standard Android XML layouts (e.g., `RelativeLayout`, `TextView`, `ImageView`). -- [ ] **Step 3: Implement AppWidgetProvider.** - - Open the generated Kotlin class extending `AppWidgetProvider`. - - In the `onUpdate` method, retrieve shared data using `HomeWidgetPlugin.getData(context)`. - - Extract values using `widgetData.getString("your_key", null)`. - - Update the UI using `RemoteViews` and `setTextViewText` or `setImageViewBitmap`. - - Call `appWidgetManager.updateAppWidget(appWidgetId, views)`. -- [ ] **Step 4: Validate.** Run Android build -> review Manifest registration errors -> fix. - -## Advanced Techniques - -### Rendering Flutter Widgets as Images -If the UI is too complex to recreate natively (e.g., custom charts), render the Flutter widget to an image and display the image in the native widget. - -1. Wrap the target Flutter widget with a `GlobalKey`. -2. Call `HomeWidget.renderFlutterWidget()`, passing the widget, a filename, and the key. -3. **iOS:** Read the file path from `UserDefaults` and render using `UIImage(contentsOfFile:)` inside a SwiftUI `Image`. -4. **Android:** Read the file path from `SharedPreferences`, decode using `BitmapFactory.decodeFile()`, and render using `setImageViewBitmap()`. - -### Using Custom Flutter Fonts (iOS Only) -If utilizing custom fonts defined in Flutter on iOS Home Screen Widgets: - -1. Extract the Flutter asset bundle path in Swift. -2. Register the font using `CTFontManagerRegisterFontsForURL`. -3. Apply the font in SwiftUI using `Font.custom()`. - -## Examples - -### Example: Flutter Data Update -```dart -import 'package:home_widget/home_widget.dart'; - -const String appGroupId = 'group.com.example.app'; -const String iOSWidgetName = 'NewsWidgets'; -const String androidWidgetName = 'NewsWidget'; - -Future updateWidgetData(String title, String description) async { - await HomeWidget.setAppGroupId(appGroupId); - await HomeWidget.saveWidgetData('headline_title', title); - await HomeWidget.saveWidgetData('headline_description', description); - await HomeWidget.updateWidget( - iOSName: iOSWidgetName, - androidName: androidWidgetName, - ); -} -``` - -### Example: iOS SwiftUI Provider & View -```swift -import WidgetKit -import SwiftUI - -struct NewsArticleEntry: TimelineEntry { - let date: Date - let title: String - let description: String -} - -struct Provider: TimelineProvider { - func placeholder(in context: Context) -> NewsArticleEntry { - NewsArticleEntry(date: Date(), title: "Loading...", description: "Loading...") - } - - func getSnapshot(in context: Context, completion: @escaping (NewsArticleEntry) -> ()) { - let userDefaults = UserDefaults(suiteName: "group.com.example.app") - let title = userDefaults?.string(forKey: "headline_title") ?? "No Title" - let description = userDefaults?.string(forKey: "headline_description") ?? "No Description" - - let entry = NewsArticleEntry(date: Date(), title: title, description: description) - completion(entry) - } - - func getTimeline(in context: Context, completion: @escaping (Timeline) -> ()) { - getSnapshot(in: context) { (entry) in - let timeline = Timeline(entries: [entry], policy: .atEnd) - completion(timeline) - } - } -} - -struct NewsWidgetsEntryView : View { - var entry: Provider.Entry - - var body: some View { - VStack(alignment: .leading) { - Text(entry.title).font(.headline) - Text(entry.description).font(.subheadline) - } - } -} -``` - -### Example: Android Kotlin Provider -```kotlin -package com.example.app.widgets - -import android.appwidget.AppWidgetManager -import android.appwidget.AppWidgetProvider -import android.content.Context -import android.widget.RemoteViews -import es.antonborri.home_widget.HomeWidgetPlugin -import com.example.app.R - -class NewsWidget : AppWidgetProvider() { - override fun onUpdate( - context: Context, - appWidgetManager: AppWidgetManager, - appWidgetIds: IntArray, - ) { - for (appWidgetId in appWidgetIds) { - val widgetData = HomeWidgetPlugin.getData(context) - val views = RemoteViews(context.packageName, R.layout.news_widget).apply { - val title = widgetData.getString("headline_title", "No Title") - setTextViewText(R.id.headline_title, title) - - val description = widgetData.getString("headline_description", "No Description") - setTextViewText(R.id.headline_description, description) - } - appWidgetManager.updateAppWidget(appWidgetId, views) - } - } -} -``` - -
-Example: iOS Custom Font Registration Helper - -```swift -// Add this to your SwiftUI View struct -var bundle: URL { - let bundle = Bundle.main - if bundle.bundleURL.pathExtension == "appex" { - var url = bundle.bundleURL.deletingLastPathComponent().deletingLastPathComponent() - url.append(component: "Frameworks/App.framework/flutter_assets") - return url - } - return bundle.bundleURL -} - -init(entry: Provider.Entry) { - self.entry = entry - CTFontManagerRegisterFontsForURL( - bundle.appending(path: "/fonts/YourCustomFont.ttf") as CFURL, - CTFontManagerScope.process, - nil - ) -} -``` -
diff --git a/skills/flutter-animating-apps/SKILL.md b/skills/flutter-animating-apps/SKILL.md deleted file mode 100644 index fce1c48..0000000 --- a/skills/flutter-animating-apps/SKILL.md +++ /dev/null @@ -1,182 +0,0 @@ ---- -name: flutter-animating-apps -description: Implements animated effects, transitions, and motion in a Flutter app. Use when adding visual feedback, shared element transitions, or physics-based animations. -metadata: - model: models/gemini-3.1-pro-preview - last_modified: Thu, 12 Mar 2026 22:16:34 GMT - ---- -# Implementing Flutter Animations - -## Contents -- [Core Concepts](#core-concepts) -- [Animation Strategies](#animation-strategies) -- [Workflows](#workflows) - - [Implementing Implicit Animations](#implementing-implicit-animations) - - [Implementing Explicit Animations](#implementing-explicit-animations) - - [Implementing Hero Transitions](#implementing-hero-transitions) - - [Implementing Physics-Based Animations](#implementing-physics-based-animations) -- [Examples](#examples) - -## Core Concepts - -Manage Flutter animations using the core typed `Animation` system. Do not manually calculate frames; rely on the framework's ticker and interpolation classes. - -* **`Animation`**: 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. -* **`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. -* **`Tween`**: 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()`. -* **`Curve`**: Apply non-linear timing (e.g., `Curves.easeIn`, `Curves.bounceOut`) to an animation using a `CurvedAnimation` or `CurveTween`. - -## Animation Strategies - -Apply conditional logic to select the correct animation approach: - -* **If animating simple property changes (size, color, opacity) without playback control:** Use **Implicit Animations** (e.g., `AnimatedContainer`, `AnimatedOpacity`, `TweenAnimationBuilder`). -* **If requiring playback control (play, pause, reverse, loop) or coordinating multiple properties:** Use **Explicit Animations** (e.g., `AnimationController` with `AnimatedBuilder` or `AnimatedWidget`). -* **If animating elements between two distinct routes:** Use **Hero Animations** (Shared Element Transitions). -* **If modeling real-world motion (e.g., snapping back after a drag):** Use **Physics-Based Animations** (e.g., `SpringSimulation`). -* **If animating a sequence of overlapping or delayed motions:** Use **Staggered Animations** (multiple `Tween`s driven by a single `AnimationController` using `Interval` curves). - -## Workflows - -### Implementing Implicit Animations - -Use this workflow for "fire-and-forget" state-driven animations. - -- [ ] **Task Progress:** - - [ ] Identify the target properties to animate (e.g., width, color). - - [ ] Replace the static widget (e.g., `Container`) with its animated counterpart (e.g., `AnimatedContainer`). - - [ ] Define the `duration` property. - - [ ] (Optional) Define the `curve` property for non-linear motion. - - [ ] Trigger the animation by updating the properties inside a `setState()` call. - - [ ] Run validator -> review UI for jank -> adjust duration/curve if necessary. - -### Implementing Explicit Animations - -Use this workflow when you need granular control over the animation lifecycle. - -- [ ] **Task Progress:** - - [ ] Add `SingleTickerProviderStateMixin` (or `TickerProviderStateMixin` for multiple controllers) to the `State` class. - - [ ] Initialize an `AnimationController` in `initState()`, providing `vsync: this` and a `duration`. - - [ ] Define a `Tween` and chain it to the controller using `.animate()`. - - [ ] Wrap the target UI in an `AnimatedBuilder` (preferred for complex trees) or subclass `AnimatedWidget`. - - [ ] Pass the `Animation` object to the `AnimatedBuilder`'s `animation` property. - - [ ] Control playback using `controller.forward()`, `controller.reverse()`, or `controller.repeat()`. - - [ ] Call `controller.dispose()` in the `dispose()` method. - - [ ] Run validator -> check for memory leaks -> ensure `dispose()` is called. - -### Implementing Hero Transitions - -Use this workflow to fly a widget between two routes. - -- [ ] **Task Progress:** - - [ ] Wrap the source widget in a `Hero` widget. - - [ ] Assign a unique, data-driven `tag` to the source `Hero`. - - [ ] Wrap the destination widget in a `Hero` widget. - - [ ] Assign the *exact same* `tag` to the destination `Hero`. - - [ ] Ensure the widget trees inside both `Hero` widgets are visually similar to prevent jarring jumps. - - [ ] Trigger the transition by pushing the destination route via `Navigator`. - -### Implementing Physics-Based Animations - -Use this workflow for gesture-driven, natural motion. - -- [ ] **Task Progress:** - - [ ] Set up an `AnimationController` (do not set a fixed duration). - - [ ] Capture gesture velocity using a `GestureDetector` (e.g., `onPanEnd` providing `DragEndDetails`). - - [ ] Convert the pixel velocity to the coordinate space of the animating property. - - [ ] Instantiate a `SpringSimulation` with mass, stiffness, damping, and the calculated velocity. - - [ ] Drive the controller using `controller.animateWith(simulation)`. - -## Examples - -
-Example: Explicit Animation (Staggered with AnimatedBuilder) - -```dart -class StaggeredAnimationDemo extends StatefulWidget { - @override - State createState() => _StaggeredAnimationDemoState(); -} - -class _StaggeredAnimationDemoState extends State with SingleTickerProviderStateMixin { - late AnimationController _controller; - late Animation _widthAnimation; - late Animation _colorAnimation; - - @override - void initState() { - super.initState(); - _controller = AnimationController( - duration: const Duration(seconds: 2), - vsync: this, - ); - - // Staggered width animation (0.0 to 0.5 interval) - _widthAnimation = Tween(begin: 50.0, end: 200.0).animate( - CurvedAnimation( - parent: _controller, - curve: const Interval(0.0, 0.5, curve: Curves.easeIn), - ), - ); - - // Staggered color animation (0.5 to 1.0 interval) - _colorAnimation = ColorTween(begin: Colors.blue, end: Colors.red).animate( - CurvedAnimation( - parent: _controller, - curve: const Interval(0.5, 1.0, curve: Curves.easeOut), - ), - ); - - _controller.forward(); - } - - @override - void dispose() { - _controller.dispose(); // CRITICAL: Prevent memory leaks - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return AnimatedBuilder( - animation: _controller, - builder: (context, child) { - return Container( - width: _widthAnimation.value, - height: 50.0, - color: _colorAnimation.value, - ); - }, - ); - } -} -``` -
- -
-Example: Custom Page Route Transition - -```dart -Route createCustomRoute(Widget destination) { - return PageRouteBuilder( - pageBuilder: (context, animation, secondaryAnimation) => destination, - transitionsBuilder: (context, animation, secondaryAnimation, child) { - const begin = Offset(0.0, 1.0); // Start from bottom - const end = Offset.zero; - const curve = Curves.easeOut; - - final tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve)); - final offsetAnimation = animation.drive(tween); - - return SlideTransition( - position: offsetAnimation, - child: child, - ); - }, - ); -} - -// Usage: Navigator.of(context).push(createCustomRoute(const NextPage())); -``` -
diff --git a/skills/flutter-architecting-apps/SKILL.md b/skills/flutter-architecting-apps/SKILL.md deleted file mode 100644 index 4e6c432..0000000 --- a/skills/flutter-architecting-apps/SKILL.md +++ /dev/null @@ -1,163 +0,0 @@ ---- -name: flutter-architecting-apps -description: Architects a Flutter application using the recommended layered approach (UI, Logic, Data). Use when structuring a new project or refactoring for scalability. -metadata: - model: models/gemini-3.1-pro-preview - last_modified: Thu, 12 Mar 2026 22:13:42 GMT - ---- -# Architecting Flutter Applications - -## Contents -- [Core Architectural Principles](#core-architectural-principles) -- [Structuring the Layers](#structuring-the-layers) -- [Implementing the Data Layer](#implementing-the-data-layer) -- [Feature Implementation Workflow](#feature-implementation-workflow) -- [Examples](#examples) - -## Core Architectural Principles - -Design Flutter applications to scale by strictly adhering to the following principles: - -* **Enforce Separation of Concerns:** Decouple UI rendering from business logic and data fetching. Organize the codebase into distinct layers (UI, Logic, Data) and further separate by feature within those layers. -* **Maintain a Single Source of Truth (SSOT):** Centralize application state and data in the Data layer. Ensure the SSOT is the only component authorized to mutate its respective data. -* **Implement Unidirectional Data Flow (UDF):** Flow state downwards from the Data layer to the UI layer. Flow events upwards from the UI layer to the Data layer. -* **Treat UI as a Function of State:** Drive the UI entirely via immutable state objects. Rebuild widgets reactively when the underlying state changes. - -## Structuring the Layers - -Separate the application into 2 to 3 distinct layers depending on complexity. Restrict communication so that a layer only interacts with the layer directly adjacent to it. - -### 1. UI Layer (Presentation) -* **Views (Widgets):** Build reusable, lean widgets. Strip all business and data-fetching logic from the widget tree. Restrict widget logic to UI-specific concerns (e.g., animations, routing, layout constraints). -* **ViewModels:** Manage the UI state. Consume domain models from the Data/Logic layers and transform them into presentation-friendly formats. Expose state to the Views and handle user interaction events. - -### 2. Logic Layer (Domain) - *Conditional* -* **If the application requires complex client-side business logic:** Implement a Logic layer containing Use Cases or Interactors. Use this layer to orchestrate interactions between multiple repositories before passing data to the UI layer. -* **If the application is a standard CRUD app:** Omit this layer. Allow ViewModels to interact directly with Repositories. - -### 3. Data Layer (Model) -* **Responsibilities:** Act as the SSOT for all application data. Handle business data, external API consumption, event processing, and data synchronization. -* **Components:** Divide the Data layer strictly into **Repositories** and **Services**. - -## Implementing the Data Layer - -### Services -* **Role:** Wrap external APIs (HTTP servers, local databases, platform plugins). -* **Implementation:** Write Services as stateless Dart classes. Do not store application state here. -* **Mapping:** Create exactly one Service class per external data source. - -### Repositories -* **Role:** Act as the SSOT for domain data. -* **Implementation:** Consume raw data from Services. Handle caching, offline synchronization, and retry logic. -* **Transformation:** Transform raw API/Service data into clean Domain Models formatted for consumption by ViewModels. - -## Feature Implementation Workflow - -Follow this sequential workflow when adding a new feature to the application. - -**Task Progress:** -- [ ] **Step 1: Define Domain Models.** Create immutable Dart classes representing the core data structures required by the feature. -- [ ] **Step 2: Implement Services.** Create stateless Service classes to handle raw data fetching (e.g., HTTP GET/POST). -- [ ] **Step 3: Implement Repositories.** Create Repository classes that consume the Services, handle caching, and return Domain Models. -- [ ] **Step 4: Implement ViewModels.** Create ViewModels that consume the Repositories. Expose immutable state and define methods (commands) for user actions. -- [ ] **Step 5: Implement Views.** Create Flutter Widgets that bind to the ViewModel state and trigger ViewModel methods on user interaction. -- [ ] **Step 6: Run Validator.** Execute unit tests for Services, Repositories, and ViewModels. Execute widget tests for Views. - * *Feedback Loop:* Review test failures -> Fix logic/mocking errors -> Re-run tests until passing. - -## Examples - -### Data Layer: Service and Repository - -```dart -// 1. Service (Stateless API Wrapper) -class UserApiService { - final HttpClient _client; - - UserApiService(this._client); - - Future> fetchUserRaw(String userId) async { - final response = await _client.get('/users/$userId'); - return response.data; - } -} - -// 2. Domain Model (Immutable) -class User { - final String id; - final String name; - - const User({required this.id, required this.name}); -} - -// 3. Repository (SSOT & Data Transformer) -class UserRepository { - final UserApiService _apiService; - User? _cachedUser; - - UserRepository(this._apiService); - - Future getUser(String userId) async { - if (_cachedUser != null && _cachedUser!.id == userId) { - return _cachedUser!; - } - - final rawData = await _apiService.fetchUserRaw(userId); - final user = User(id: rawData['id'], name: rawData['name']); - - _cachedUser = user; // Cache data - return user; - } -} -``` - -### UI Layer: ViewModel and View - -```dart -// 4. ViewModel (State Management) -class UserViewModel extends ChangeNotifier { - final UserRepository _userRepository; - - User? user; - bool isLoading = false; - String? error; - - UserViewModel(this._userRepository); - - Future loadUser(String userId) async { - isLoading = true; - error = null; - notifyListeners(); - - try { - user = await _userRepository.getUser(userId); - } catch (e) { - error = e.toString(); - } finally { - isLoading = false; - notifyListeners(); - } - } -} - -// 5. View (Lean UI) -class UserProfileView extends StatelessWidget { - final UserViewModel viewModel; - - const UserProfileView({Key? key, required this.viewModel}) : super(key: key); - - @override - Widget build(BuildContext context) { - return ListenableBuilder( - listenable: viewModel, - builder: (context, child) { - if (viewModel.isLoading) return const CircularProgressIndicator(); - if (viewModel.error != null) return Text('Error: ${viewModel.error}'); - if (viewModel.user == null) return const Text('No user data.'); - - return Text('Hello, ${viewModel.user!.name}'); - }, - ); - } -} -``` diff --git a/skills/flutter-building-forms/SKILL.md b/skills/flutter-building-forms/SKILL.md deleted file mode 100644 index 05faa45..0000000 --- a/skills/flutter-building-forms/SKILL.md +++ /dev/null @@ -1,122 +0,0 @@ ---- -name: flutter-building-forms -description: Builds Flutter forms with validation and user input handling. Use when creating login screens, data entry forms, or any multi-field user input. -metadata: - model: models/gemini-3.1-pro-preview - last_modified: Thu, 12 Mar 2026 22:15:24 GMT - ---- -# Building Validated Forms - -## Contents -- [Form Architecture](#form-architecture) -- [Field Validation](#field-validation) -- [Workflow: Implementing a Validated Form](#workflow-implementing-a-validated-form) -- [Examples](#examples) - -## Form Architecture - -Implement forms using a `Form` widget to group and validate multiple input fields together. - -- **Use a StatefulWidget:** Always host your `Form` inside a `StatefulWidget`. -- **Persist the GlobalKey:** Instantiate a `GlobalKey` 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. -- **Bind the Key:** Pass the `GlobalKey` to the `key` property of the `Form` widget. This uniquely identifies the form and provides access to the `FormState` for validation and submission. -- **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. - -## Field Validation - -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`. - -- **Implement the Validator:** Provide a `validator()` callback function to each `TextFormField`. -- **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. -- **Return Null for Success:** If the input passes validation, you must return `null`. - -## Workflow: Implementing a Validated Form - -Follow this sequential workflow to implement and validate a form. Copy the checklist to track your progress. - -**Task Progress:** -- [ ] 1. Create a `StatefulWidget` and its corresponding `State` class. -- [ ] 2. Instantiate `final _formKey = GlobalKey();` in the `State` class. -- [ ] 3. Return a `Form` widget in the `build` method and assign `key: _formKey`. -- [ ] 4. Add `TextFormField` widgets as descendants of the `Form`. -- [ ] 5. Write a `validator` function for each `TextFormField` (return `String` on error, `null` on success). -- [ ] 6. Add a submit button (e.g., `ElevatedButton`). -- [ ] 7. Implement the validation check in the button's `onPressed` callback using `_formKey.currentState!.validate()`. - -### Validation Decision Logic - -When the user triggers the submit action, execute the following conditional logic: - -1. Call `_formKey.currentState!.validate()`. -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`). -3. **If `false` (Invalid):** One or more validators returned an error string. The `FormState` automatically rebuilds the UI to display the error messages. -4. **Feedback Loop:** Run validator -> review errors -> fix. The user must adjust their input and resubmit until `validate()` returns `true`. - -## Examples - -### Complete Validated Form Implementation - -Use the following pattern to implement a robust, validated form. - -```dart -import 'package:flutter/material.dart'; - -class UserRegistrationForm extends StatefulWidget { - const UserRegistrationForm({super.key}); - - @override - State createState() => _UserRegistrationFormState(); -} - -class _UserRegistrationFormState extends State { - // 1. Persist the GlobalKey in the State class - final _formKey = GlobalKey(); - - @override - Widget build(BuildContext context) { - // 2. Bind the key to the Form - return Form( - key: _formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // 3. Add TextFormFields with validators - TextFormField( - decoration: const InputDecoration( - labelText: 'Username', - hintText: 'Enter your username', - ), - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter a username'; // Error state - } - if (value.length < 4) { - return 'Username must be at least 4 characters'; // Error state - } - return null; // Valid state - }, - ), - const SizedBox(height: 16), - // 4. Add the submit button - ElevatedButton( - onPressed: () { - // 5. Trigger validation logic - if (_formKey.currentState!.validate()) { - // Form is valid: Process data - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Processing Data')), - ); - } else { - // Form is invalid: Errors are automatically displayed - debugPrint('Form validation failed.'); - } - }, - child: const Text('Submit'), - ), - ], - ), - ); - } -} -``` diff --git a/skills/flutter-building-layouts/SKILL.md b/skills/flutter-building-layouts/SKILL.md deleted file mode 100644 index bda2658..0000000 --- a/skills/flutter-building-layouts/SKILL.md +++ /dev/null @@ -1,128 +0,0 @@ ---- -name: flutter-building-layouts -description: Builds Flutter layouts using the constraint system and layout widgets. Use when creating or refining the UI structure of a Flutter application. -metadata: - model: models/gemini-3.1-pro-preview - last_modified: Thu, 12 Mar 2026 22:14:15 GMT - ---- -# Architecting Flutter Layouts - -## Contents -- [Core Layout Principles](#core-layout-principles) -- [Structural Widgets](#structural-widgets) -- [Adaptive and Responsive Design](#adaptive-and-responsive-design) -- [Workflow: Implementing a Complex Layout](#workflow-implementing-a-complex-layout) -- [Examples](#examples) - -## Core Layout Principles - -Master the fundamental Flutter layout rule: **Constraints go down. Sizes go up. Parent sets position.** - -* **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. -* **Pass Sizes Up:** Calculate the child Widget's desired size within the given constraints and pass this size back up to the parent. -* **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. -* **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. - -## Structural Widgets - -Select the appropriate structural Widget based on the required spatial arrangement. - -* **Use `Row` and `Column`:** Implement `Row` for horizontal linear layouts and `Column` for vertical linear layouts. Control child alignment using `mainAxisAlignment` and `crossAxisAlignment`. -* **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. -* **Use `Container`:** Wrap Widgets in a `Container` when you need to apply padding, margins, borders, or background colors. -* **Use `Stack`:** Implement `Stack` when Widgets must overlap on the Z-axis. Use `Positioned` to anchor children to specific edges of the `Stack`. -* **Use `SizedBox`:** Enforce strict, tight constraints on a child Widget by wrapping it in a `SizedBox` with explicit `width` and `height` values. - -## Adaptive and Responsive Design - -Apply conditional logic to handle varying screen sizes and form factors. - -* **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. -* **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. - -## Workflow: Implementing a Complex Layout - -Follow this sequential workflow to architect and implement robust Flutter layouts. - -### Task Progress -- [ ] **Phase 1: Visual Deconstruction** - - [ ] Break down the target UI into a hierarchy of rows, columns, and grids. - - [ ] Identify overlapping elements (requiring `Stack`). - - [ ] Identify scrolling regions (requiring `ListView` or `SingleChildScrollView`). -- [ ] **Phase 2: Constraint Planning** - - [ ] Determine which Widgets require tight constraints (fixed size) vs. loose constraints (flexible size). - - [ ] Identify potential unbounded constraint risks (e.g., a `ListView` inside a `Column`). -- [ ] **Phase 3: Implementation** - - [ ] Build the layout from the outside in, starting with the `Scaffold` and primary structural Widgets. - - [ ] Extract deeply nested layout sections into separate, stateless Widgets to maintain readability. -- [ ] **Phase 4: Validation and Feedback Loop** - - [ ] Run the application on target devices/simulators. - - [ ] **Run validator -> review errors -> fix:** Open the Flutter Inspector. Enable "Debug Paint" to visualize render boxes. - - [ ] Check for yellow/black striped overflow warnings. - - [ ] If overflow occurs: Wrap the overflowing Widget in `Expanded` (if inside a flex box) or wrap the parent in a scrollable Widget. - -## Examples - -### Example: Resolving Unbounded Constraints in Flex Boxes - -**Anti-pattern:** Placing a `ListView` directly inside a `Column` causes an unbounded height exception because the `Column` provides infinite vertical space to the `ListView`. - -```dart -// BAD: Throws unbounded height exception -Column( - children: [ - Text('Header'), - ListView( - children: [/* items */], - ), - ], -) -``` - -**Implementation:** Wrap the `ListView` in an `Expanded` Widget to bound its height to the remaining space in the `Column`. - -```dart -// GOOD: ListView is constrained to remaining space -Column( - children: [ - Text('Header'), - Expanded( - child: ListView( - children: [/* items */], - ), - ), - ], -) -``` - -### Example: Responsive Layout with LayoutBuilder - -Implement `LayoutBuilder` to conditionally render different structural Widgets based on available width. - -```dart -Widget buildAdaptiveLayout(BuildContext context) { - return LayoutBuilder( - builder: (context, constraints) { - // Conditional logic based on screen width - if (constraints.maxWidth > 600) { - // Tablet/Desktop: Side-by-side layout - return Row( - children: [ - SizedBox(width: 250, child: SidebarWidget()), - Expanded(child: MainContentWidget()), - ], - ); - } else { - // Mobile: Stacked layout with navigation - return Column( - children: [ - Expanded(child: MainContentWidget()), - BottomNavigationBarWidget(), - ], - ); - } - }, - ); -} -``` diff --git a/skills/flutter-building-plugins/SKILL.md b/skills/flutter-building-plugins/SKILL.md deleted file mode 100644 index 5b51201..0000000 --- a/skills/flutter-building-plugins/SKILL.md +++ /dev/null @@ -1,206 +0,0 @@ ---- -name: flutter-building-plugins -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. -metadata: - model: models/gemini-3.1-pro-preview - last_modified: Thu, 12 Mar 2026 22:21:35 GMT - ---- -# Developing Flutter Plugins - -## Contents -- [Architecture & Design Patterns](#architecture--design-patterns) -- [Workflow: Creating a New Plugin](#workflow-creating-a-new-plugin) -- [Workflow: Implementing Android Platform Code](#workflow-implementing-android-platform-code) -- [Workflow: Implementing Windows Platform Code](#workflow-implementing-windows-platform-code) -- [Workflow: Adding Platforms to an Existing Plugin](#workflow-adding-platforms-to-an-existing-plugin) -- [Examples](#examples) - -## Architecture & Design Patterns - -### Federated Plugins -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: -1. **App-facing interface:** The primary package users depend on. It exports the public API. -2. **Platform interface:** The package defining the common interface that all platform implementations must implement. -3. **Platform implementations:** Independent packages containing platform-specific code (e.g., `my_plugin_android`, `my_plugin_windows`). - -### FFI vs. Standard Plugins -Choose the correct plugin template based on your native interoperability requirements: -* **Standard Plugins (`--template=plugin`):** Use for accessing platform-specific APIs (e.g., Android SDK, iOS frameworks) via Method Channels. -* **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. - * *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. - -## Workflow: Creating a New Plugin - -Follow this workflow to initialize a new plugin package. - -**Task Progress:** -- [ ] Determine if the plugin requires FFI or standard Method Channels. -- [ ] Execute the appropriate `flutter create` command. -- [ ] Verify the generated directory structure. - -**Conditional Initialization:** -* **If creating a STANDARD plugin:** - Run the following command, specifying your supported platforms, organization, and preferred languages (defaults are Swift and Kotlin): - ```bash - flutter create --template=plugin \ - --platforms=android,ios,web,linux,macos,windows \ - --org com.example.organization \ - -i objc -a java \ - my_plugin - ``` -* **If creating an FFI plugin:** - 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`): - ```bash - flutter create --template=plugin_ffi my_ffi_plugin - ``` - -## Workflow: Implementing Android Platform Code - -Always edit Android platform code using Android Studio to ensure proper code completion and Gradle synchronization. - -**Task Progress:** -- [ ] Run initial build to generate necessary Gradle files. -- [ ] Open the Android module in Android Studio. -- [ ] Implement `FlutterPlugin` and lifecycle-aware interfaces. -- [ ] Refactor legacy `registerWith` logic. -- [ ] Run validator -> review errors -> fix. - -1. **Generate Build Files:** - Build the code at least once before editing to resolve dependencies. - ```bash - cd example - flutter build apk --config-only - ``` -2. **Open in IDE:** - Launch Android Studio and open the `example/android/build.gradle` or `example/android/build.gradle.kts` file. -3. **Locate Source:** - Navigate to your plugin's source code at `java//`. -4. **Implement V2 Embedding:** - * Implement the `FlutterPlugin` interface. - * Ensure your plugin class has a public constructor. - * 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. -5. **Implement Lifecycle Interfaces:** - * **If your plugin requires an `Activity` reference:** Implement the `ActivityAware` interface and handle the `onAttachedToActivity`, `onDetachedFromActivityForConfigChanges`, `onReattachedToActivityForConfigChanges`, and `onDetachedFromActivity` callbacks. - * **If your plugin runs in a background `Service`:** Implement the `ServiceAware` interface. -6. **Update Example App:** - Ensure the example app's `MainActivity.java` extends the v2 embedding `io.flutter.embedding.android.FlutterActivity`. -7. **Document API:** - Document all non-overridden public members in your Android implementation. - -## Workflow: Implementing Windows Platform Code - -Always edit Windows platform code using Visual Studio. - -**Task Progress:** -- [ ] Run initial build to generate the Visual Studio solution. -- [ ] Open the solution in Visual Studio. -- [ ] Implement C++ logic. -- [ ] Rebuild the solution. - -1. **Generate Build Files:** - ```bash - cd example - flutter build windows - ``` -2. **Open in IDE:** - Launch Visual Studio and open the `example/build/windows/hello_example.sln` file. -3. **Locate Source:** - Navigate to `hello_plugin/Source Files` and `hello_plugin/Header Files` in the Solution Explorer. -4. **Rebuild:** - 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. - -## Workflow: Adding Platforms to an Existing Plugin - -Use this workflow to retrofit an existing plugin with support for additional platforms. - -**Task Progress:** -- [ ] Run the platform addition command. -- [ ] Update iOS/macOS podspecs (if applicable). -- [ ] Implement the platform-specific code. - -1. **Run Create Command:** - Navigate to the root directory of your existing plugin and run: - ```bash - flutter create --template=plugin --platforms=web,macos . - ``` -2. **Update Podspecs:** - If adding iOS or macOS support, open the generated `.podspec` file and configure the required dependencies and deployment targets. - -## Examples - -### Android V2 Embedding Implementation -High-fidelity example of an Android plugin implementing `FlutterPlugin` and `ActivityAware` while maintaining legacy compatibility. - -```java -package com.example.myplugin; - -import androidx.annotation.NonNull; -import io.flutter.embedding.engine.plugins.FlutterPlugin; -import io.flutter.embedding.engine.plugins.activity.ActivityAware; -import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; -import io.flutter.plugin.common.MethodCall; -import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.MethodChannel.MethodCallHandler; -import io.flutter.plugin.common.MethodChannel.Result; -import io.flutter.plugin.common.PluginRegistry.Registrar; - -/** MyPlugin */ -public class MyPlugin implements FlutterPlugin, MethodCallHandler, ActivityAware { - private MethodChannel channel; - - // Public constructor required for v2 embedding - public MyPlugin() {} - - @Override - public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { - setupChannel(flutterPluginBinding.getBinaryMessenger()); - } - - // Legacy v1 embedding support - public static void registerWith(Registrar registrar) { - MyPlugin plugin = new MyPlugin(); - plugin.setupChannel(registrar.messenger()); - } - - // Shared initialization logic - private void setupChannel(BinaryMessenger messenger) { - channel = new MethodChannel(messenger, "my_plugin"); - channel.setMethodCallHandler(this); - } - - @Override - public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { - if (call.method.equals("getPlatformVersion")) { - result.success("Android " + android.os.Build.VERSION.RELEASE); - } else { - result.notImplemented(); - } - } - - @Override - public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { - channel.setMethodCallHandler(null); - } - - @Override - public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { - // Handle Activity attachment - } - - @Override - public void onDetachedFromActivityForConfigChanges() { - // Handle config changes - } - - @Override - public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { - // Handle reattachment - } - - @Override - public void onDetachedFromActivity() { - // Clean up Activity references - } -} -``` diff --git a/skills/flutter-caching-data/SKILL.md b/skills/flutter-caching-data/SKILL.md deleted file mode 100644 index 0889334..0000000 --- a/skills/flutter-caching-data/SKILL.md +++ /dev/null @@ -1,168 +0,0 @@ ---- -name: flutter-caching-data -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. -metadata: - model: models/gemini-3.1-pro-preview - last_modified: Thu, 12 Mar 2026 22:19:54 GMT - ---- -# Implementing Flutter Caching and Offline-First Architectures - -## Contents -- [Selecting a Caching Strategy](#selecting-a-caching-strategy) -- [Implementing Offline-First Data Synchronization](#implementing-offline-first-data-synchronization) -- [Managing File System and SQLite Persistence](#managing-file-system-and-sqlite-persistence) -- [Optimizing UI, Scroll, and Image Caching](#optimizing-ui-scroll-and-image-caching) -- [Caching the FlutterEngine (Android)](#caching-the-flutterengine-android) -- [Workflows](#workflows) - -## Selecting a Caching Strategy - -Apply the appropriate caching mechanism based on the data lifecycle and size requirements. - -* **If storing small, non-critical UI states or preferences:** Use `shared_preferences`. -* **If storing large, structured datasets:** Use on-device databases (SQLite via `sqflite`, Drift, Hive CE, or Isar). -* **If storing binary data or large media:** Use file system caching via `path_provider`. -* **If retaining user session state (navigation, scroll positions):** Implement Flutter's built-in state restoration to sync the Element tree with the engine. -* **If optimizing Android initialization:** Pre-warm and cache the `FlutterEngine`. - -## Implementing Offline-First Data Synchronization - -Design repositories as the single source of truth, combining local databases and remote API clients. - -### Read Operations (Stream Approach) -Yield local data immediately for fast UI rendering, then fetch remote data, update the local cache, and yield the fresh data. - -```dart -Stream getUserProfile() async* { - // 1. Yield local cache first - final localProfile = await _databaseService.fetchUserProfile(); - if (localProfile != null) yield localProfile; - - // 2. Fetch remote, update cache, yield fresh data - try { - final remoteProfile = await _apiClientService.getUserProfile(); - await _databaseService.updateUserProfile(remoteProfile); - yield remoteProfile; - } catch (e) { - // Handle network failure; UI already has local data - } -} -``` - -### Write Operations -Determine the write strategy based on data criticality: -* **If strict server synchronization is required (Online-only):** Attempt the API call first. Only update the local database if the API call succeeds. -* **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. - -### Background Synchronization -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. - -## Managing File System and SQLite Persistence - -### File System Caching -Use `path_provider` to locate the correct directory. -* Use `getApplicationDocumentsDirectory()` for persistent data. -* Use `getTemporaryDirectory()` for cache data the OS can clear. - -```dart -Future get _localFile async { - final directory = await getApplicationDocumentsDirectory(); - return File('${directory.path}/cache.txt'); -} -``` - -### SQLite Persistence -Use `sqflite` for relational data caching. Always use `whereArgs` to prevent SQL injection. - -```dart -Future updateCachedRecord(Record record) async { - final db = await database; - await db.update( - 'records', - record.toMap(), - where: 'id = ?', - whereArgs: [record.id], // NEVER use string interpolation here - ); -} -``` - -## Optimizing UI, Scroll, and Image Caching - -### Image Caching -Image I/O and decompression are expensive. -* Use the `cached_network_image` package to handle file-system caching of remote images. -* **Custom ImageProviders:** If implementing a custom `ImageProvider`, override `createStream()` and `resolveStreamForKey()` instead of the deprecated `resolve()` method. -* **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. - -### Scroll Caching -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. - -```dart -// Correct implementation -ListView( - scrollCacheExtent: const ScrollCacheExtent.pixels(500.0), - children: // ... -) - -Viewport( - scrollCacheExtent: const ScrollCacheExtent.viewport(0.5), - slivers: // ... -) -``` - -### Widget Caching -* Avoid overriding `operator ==` on `Widget` objects. It causes O(N²) behavior during rebuilds. -* **Exception:** You may override `operator ==` *only* on leaf widgets (no children) where comparing properties is significantly faster than rebuilding, and the properties rarely change. -* Prefer using `const` constructors to allow the framework to short-circuit rebuilds automatically. - -## Caching the FlutterEngine (Android) - -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. - -1. Instantiate and pre-warm the engine in the `Application` class. -2. Store it in the `FlutterEngineCache`. -3. Retrieve it using `withCachedEngine` in the `FlutterActivity` or `FlutterFragment`. - -```kotlin -// 1. Pre-warm in Application class -val flutterEngine = FlutterEngine(this) -flutterEngine.navigationChannel.setInitialRoute("/cached_route") -flutterEngine.dartExecutor.executeDartEntrypoint(DartEntrypoint.createDefault()) - -// 2. Cache the engine -FlutterEngineCache.getInstance().put("my_engine_id", flutterEngine) - -// 3. Use in Activity/Fragment -startActivity( - FlutterActivity.withCachedEngine("my_engine_id").build(this) -) -``` -*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.* - -## Workflows - -### Workflow: Implementing an Offline-First Repository -Follow these steps to implement a robust offline-first data layer. - -- [ ] **Task Progress:** - - [ ] Define the data model with a `synchronized` boolean flag (default `false`). - - [ ] Implement the local `DatabaseService` (SQLite/Hive) with CRUD operations. - - [ ] Implement the remote `ApiClientService` for network requests. - - [ ] Create the `Repository` class combining both services. - - [ ] Implement the read method returning a `Stream` (yield local, fetch remote, update local, yield remote). - - [ ] Implement the write method (write local, attempt remote, update `synchronized` flag). - - [ ] Implement a background sync function to process records where `synchronized == false`. - - [ ] Run validator -> review errors -> fix (Test offline behavior by disabling network). - -### Workflow: Pre-warming the Android FlutterEngine -Follow these steps to cache the FlutterEngine for seamless Android integration. - -- [ ] **Task Progress:** - - [ ] Locate the Android `Application` class (create one if it doesn't exist and register in `AndroidManifest.xml`). - - [ ] Instantiate a new `FlutterEngine`. - - [ ] (Optional) Set the initial route via `navigationChannel.setInitialRoute()`. - - [ ] Execute the Dart entrypoint via `dartExecutor.executeDartEntrypoint()`. - - [ ] Store the engine in `FlutterEngineCache.getInstance().put()`. - - [ ] Update the target `FlutterActivity` or `FlutterFragment` to use `.withCachedEngine("id")`. - - [ ] Run validator -> review errors -> fix (Verify no blank screen appears during transition). diff --git a/skills/flutter-embedding-native-views/SKILL.md b/skills/flutter-embedding-native-views/SKILL.md deleted file mode 100644 index ced9b0d..0000000 --- a/skills/flutter-embedding-native-views/SKILL.md +++ /dev/null @@ -1,197 +0,0 @@ ---- -name: flutter-embedding-native-views -description: Embeds native Android, iOS, or macOS views into a Flutter app. Use when integrating complex native components like maps or web views. -metadata: - model: models/gemini-3.1-pro-preview - last_modified: Thu, 12 Mar 2026 22:20:28 GMT - ---- -# Integrating Platform Views and Web Content - -## Contents -- [Platform Views Architecture](#platform-views-architecture) -- [Web Embedding Architecture](#web-embedding-architecture) -- [Workflow: Implementing Android Platform Views](#workflow-implementing-android-platform-views) -- [Workflow: Implementing iOS Platform Views](#workflow-implementing-ios-platform-views) -- [Workflow: Embedding Flutter in Web Applications](#workflow-embedding-flutter-in-web-applications) -- [Examples](#examples) - -## Platform Views Architecture - -Platform Views allow embedding native views (Android, iOS, macOS) directly into a Flutter application, enabling the application of transforms, clips, and opacity from Dart. - -### Android Implementations (API 23+) -Choose the appropriate implementation based on your performance and fidelity requirements: -* **Hybrid Composition:** Renders Flutter content into a texture and uses `SurfaceFlinger` to compose both. - * *Pros:* Best performance and fidelity for Android views. - * *Cons:* Lowers overall application FPS. Certain Flutter widget transformations will not work. -* **Texture Layer (Texture Layer Hybrid Composition):** Renders Platform Views into a texture. Flutter draws them via the texture and renders its own content directly into a Surface. - * *Pros:* Best performance for Flutter rendering. All transformations work correctly. - * *Cons:* Quick scrolling (e.g., WebViews) can be janky. `SurfaceView` is problematic (breaks accessibility). Text magnifiers break unless Flutter is rendered into a `TextureView`. - -### iOS & macOS Implementations -* **iOS:** Uses Hybrid Composition exclusively. The native `UIView` is appended to the view hierarchy. - * *Limitations:* `ShaderMask` and `ColorFiltered` widgets are not supported. `BackdropFilter` has composition limitations. -* **macOS:** Uses Hybrid Composition (`NSView`). - * *Limitations:* Not fully functional in current releases (e.g., gesture support is unavailable). - -### Performance Mitigation -Mitigate performance drops during complex Dart animations by rendering a screenshot of the native view as a placeholder texture while the animation runs. - -## Web Embedding Architecture - -Embed Flutter into existing web applications (Vanilla JS, React, Angular, etc.) using either Full Page mode or Embedded (Multi-view) mode. - -* **Full Page Mode:** Flutter takes over the entire browser window. Use an `iframe` if you need to constrain the Flutter app without modifying the Flutter bootstrap process. -* **Embedded Mode (Multi-view):** Render Flutter into specific HTML elements (`div`s). Requires `multiViewEnabled: true` during engine initialization. - * Manage views from JavaScript using `app.addView()` and `app.removeView()`. - * In Dart, replace `runApp` with `runWidget`. - * Manage the dynamic list of views using `WidgetsBinding.instance.platformDispatcher.views` and render them using `ViewCollection` and `View` widgets. - -## Workflow: Implementing Android Platform Views - -Follow this sequential workflow to implement a Platform View on Android. - -**Task Progress:** -- [ ] 1. Determine the composition mode (Hybrid vs. Texture Layer). -- [ ] 2. Implement the Dart widget. -- [ ] 3. Implement the native Android View and Factory. -- [ ] 4. Register the Platform View in the Android host. -- [ ] 5. Run validator -> review rendering -> fix manual invalidation issues. - -### 1. Dart Implementation -If using **Hybrid Composition**, use `PlatformViewLink`, `AndroidViewSurface`, and `PlatformViewsService.initSurfaceAndroidView`. -If using **Texture Layer**, use the `AndroidView` widget. - -### 2. Native Implementation -Create a class implementing `io.flutter.plugin.platform.PlatformView` that returns your native `android.view.View`. -Create a factory extending `PlatformViewFactory` to instantiate your view. - -### 3. Registration -Register the factory in your `MainActivity.kt` (or plugin) using `flutterEngine.platformViewsController.registry.registerViewFactory`. - -*Note: If your native view uses `SurfaceView` or `SurfaceTexture`, manually call `invalidate` on the View or its parent when content changes, as they do not invalidate themselves automatically.* - -## Workflow: Implementing iOS Platform Views - -Follow this sequential workflow to implement a Platform View on iOS. - -**Task Progress:** -- [ ] 1. Implement the Dart widget using `UiKitView`. -- [ ] 2. Implement the native iOS View (`FlutterPlatformView`) and Factory (`FlutterPlatformViewFactory`). -- [ ] 3. Register the Platform View in `AppDelegate.swift` or the plugin registrar. -- [ ] 4. Run validator -> review composition limitations -> fix unsupported filters. - -## Workflow: Embedding Flutter in Web Applications - -Follow this sequential workflow to embed Flutter into an existing web DOM. - -**Task Progress:** -- [ ] 1. Update `flutter_bootstrap.js` to enable multi-view. -- [ ] 2. Update `main.dart` to use `runWidget` and `ViewCollection`. -- [ ] 3. Implement JavaScript logic to add/remove host elements. -- [ ] 4. Run validator -> review view constraints -> fix CSS conflicts. - -### 1. JavaScript Configuration -In `flutter_bootstrap.js`, initialize the engine with `multiViewEnabled: true`. -Use the returned `app` object to add views: `app.addView({ hostElement: document.getElementById('my-div') })`. - -### 2. Dart Configuration -Replace `runApp()` with `runWidget()`. -Create a root widget that listens to `WidgetsBindingObserver.didChangeMetrics`. -Map over `WidgetsBinding.instance.platformDispatcher.views` to create a `View` widget for each attached `FlutterView`, and wrap them all in a `ViewCollection`. - -## Examples - -### Example: Android Texture Layer (Dart) -```dart -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - -class NativeAndroidView extends StatelessWidget { - @override - Widget build(BuildContext context) { - const String viewType = 'my_native_view'; - final Map creationParams = {}; - - return AndroidView( - viewType: viewType, - layoutDirection: TextDirection.ltr, - creationParams: creationParams, - creationParamsCodec: const StandardMessageCodec(), - ); - } -} -``` - -### Example: Web Multi-View Initialization (JavaScript) -```javascript -_flutter.loader.load({ - onEntrypointLoaded: async function(engineInitializer) { - let engine = await engineInitializer.initializeEngine({ - multiViewEnabled: true, - }); - let app = await engine.runApp(); - - // Add a view to a specific DOM element - let viewId = app.addView({ - hostElement: document.querySelector('#flutter-host-container'), - initialData: { customData: 'Hello from JS' } - }); - } -}); -``` - -### Example: Web Multi-View Root Widget (Dart) -```dart -import 'dart:ui' show FlutterView; -import 'package:flutter/widgets.dart'; - -void main() { - runWidget(MultiViewApp(viewBuilder: (context) => const MyEmbeddedWidget())); -} - -class MultiViewApp extends StatefulWidget { - final WidgetBuilder viewBuilder; - const MultiViewApp({super.key, required this.viewBuilder}); - - @override - State createState() => _MultiViewAppState(); -} - -class _MultiViewAppState extends State with WidgetsBindingObserver { - Map _views = {}; - - @override - void initState() { - super.initState(); - WidgetsBinding.instance.addObserver(this); - _updateViews(); - } - - @override - void didChangeMetrics() => _updateViews(); - - void _updateViews() { - final newViews = {}; - for (final FlutterView view in WidgetsBinding.instance.platformDispatcher.views) { - newViews[view.viewId] = _views[view.viewId] ?? View( - view: view, - child: Builder(builder: widget.viewBuilder), - ); - } - setState(() => _views = newViews); - } - - @override - void dispose() { - WidgetsBinding.instance.removeObserver(this); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return ViewCollection(views: _views.values.toList(growable: false)); - } -} -``` diff --git a/skills/flutter-handling-concurrency/SKILL.md b/skills/flutter-handling-concurrency/SKILL.md deleted file mode 100644 index c419fc2..0000000 --- a/skills/flutter-handling-concurrency/SKILL.md +++ /dev/null @@ -1,179 +0,0 @@ ---- -name: flutter-handling-concurrency -description: Executes long-running tasks in background isolates to keep the UI responsive. Use when performing heavy computations or parsing large datasets. -metadata: - model: models/gemini-3.1-pro-preview - last_modified: Thu, 12 Mar 2026 22:23:14 GMT - ---- -# Managing Dart Concurrency and Isolates - -## Contents -- [Core Concepts](#core-concepts) -- [Decision Matrix: Async vs. Isolates](#decision-matrix-async-vs-isolates) -- [Workflows](#workflows) - - [Implementing Standard Asynchronous UI](#implementing-standard-asynchronous-ui) - - [Offloading Short-Lived Heavy Computation](#offloading-short-lived-heavy-computation) - - [Establishing Long-Lived Worker Isolates](#establishing-long-lived-worker-isolates) -- [Examples](#examples) - -## Core Concepts - -Dart utilizes a single-threaded execution model driven by an Event Loop (comparable to the iOS main loop). By default, all Flutter application code runs on the Main Isolate. - -* **Asynchronous Operations (`async`/`await`):** Use for non-blocking I/O tasks (network requests, file access). The Event Loop continues processing other events while waiting for the `Future` to complete. -* **Isolates:** Dart's implementation of lightweight threads. Isolates possess their own isolated memory and do not share state. They communicate exclusively via message passing. -* **Main Isolate:** The default thread where UI rendering and event handling occur. Blocking this isolate causes UI freezing (jank). -* **Worker Isolate:** A spawned isolate used to offload CPU-bound tasks (e.g., decoding large JSON blobs) to prevent Main Isolate blockage. - -## Decision Matrix: Async vs. Isolates - -Apply the following conditional logic to determine the correct concurrency approach: - -* **If** the task is I/O bound (e.g., HTTP request, database read) -> **Use `async`/`await`** on the Main Isolate. -* **If** the task is CPU-bound but executes quickly (< 16ms) -> **Use `async`/`await`** on the Main Isolate. -* **If** the task is CPU-bound, takes significant time, and runs once (e.g., parsing a massive JSON payload) -> **Use `Isolate.run()`**. -* **If** the task requires continuous or repeated background processing with multiple messages passed over time -> **Use `Isolate.spawn()` with `ReceivePort` and `SendPort`**. - -## Workflows - -### Implementing Standard Asynchronous UI - -Use this workflow to fetch and display non-blocking asynchronous data. - -**Task Progress:** -- [ ] Mark the data-fetching function with the `async` keyword. -- [ ] Return a `Future` from the function. -- [ ] Use the `await` keyword to yield execution until the operation completes. -- [ ] Wrap the UI component in a `FutureBuilder` (or `StreamBuilder` for streams). -- [ ] Handle `ConnectionState.waiting`, `hasError`, and `hasData` states within the builder. -- [ ] Run validator -> review UI for loading indicators -> fix missing states. - -### Offloading Short-Lived Heavy Computation - -Use this workflow for one-off, CPU-intensive tasks using Dart 2.19+. - -**Task Progress:** -- [ ] Identify the CPU-bound operation blocking the Main Isolate. -- [ ] Extract the computation into a standalone callback function. -- [ ] Ensure the callback function signature accepts exactly one required, unnamed argument (as per specific architectural constraints). -- [ ] Invoke `Isolate.run()` passing the callback. -- [ ] `await` the result of `Isolate.run()` in the Main Isolate. -- [ ] Assign the returned value to the application state. - -### Establishing Long-Lived Worker Isolates - -Use this workflow for persistent background processes requiring continuous bidirectional communication. - -**Task Progress:** -- [ ] Instantiate a `ReceivePort` on the Main Isolate to listen for messages. -- [ ] Spawn the worker isolate using `Isolate.spawn()`, passing the `ReceivePort.sendPort` as the initial message. -- [ ] In the worker isolate, instantiate its own `ReceivePort`. -- [ ] Send the worker's `SendPort` back to the Main Isolate via the initial port. -- [ ] Store the worker's `SendPort` in the Main Isolate for future message dispatching. -- [ ] Implement listeners on both `ReceivePort` instances to handle incoming messages. -- [ ] Run validator -> review memory leaks -> ensure ports are closed when the isolate is no longer needed. - -## Examples - -### Example 1: Asynchronous UI with FutureBuilder - -```dart -// 1. Define the async operation -Future fetchUserData() async { - await Future.delayed(const Duration(seconds: 2)); // Simulate network I/O - return "User Data Loaded"; -} - -// 2. Consume in the UI -Widget build(BuildContext context) { - return FutureBuilder( - future: fetchUserData(), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const CircularProgressIndicator(); - } else if (snapshot.hasError) { - return Text('Error: ${snapshot.error}'); - } else { - return Text('Result: ${snapshot.data}'); - } - }, - ); -} -``` - -### Example 2: Short-Lived Isolate (`Isolate.run`) - -```dart -import 'dart:isolate'; -import 'dart:convert'; - -// 1. Define the heavy computation callback -// Note: Adhering to the strict single-argument signature requirement. -List decodeHeavyJson(String jsonString) { - return jsonDecode(jsonString) as List; -} - -// 2. Offload to a worker isolate -Future> processDataInBackground(String rawJson) async { - // Isolate.run spawns the isolate, runs the computation, returns the value, and exits. - final result = await Isolate.run(() => decodeHeavyJson(rawJson)); - return result; -} -``` - -### Example 3: Long-Lived Isolate (`ReceivePort` / `SendPort`) - -```dart -import 'dart:isolate'; - -class WorkerManager { - late SendPort _workerSendPort; - final ReceivePort _mainReceivePort = ReceivePort(); - Isolate? _isolate; - - Future initialize() async { - // 1. Spawn isolate and pass the Main Isolate's SendPort - _isolate = await Isolate.spawn(_workerEntry, _mainReceivePort.sendPort); - - // 2. Listen for messages from the Worker Isolate - _mainReceivePort.listen((message) { - if (message is SendPort) { - // First message is the Worker's SendPort - _workerSendPort = message; - _startCommunication(); - } else { - // Subsequent messages are data payloads - print('Main Isolate received: $message'); - } - }); - } - - void _startCommunication() { - // Send data to the worker - _workerSendPort.send("Process this data"); - } - - // 3. Worker Isolate Entry Point - static void _workerEntry(SendPort mainSendPort) { - final workerReceivePort = ReceivePort(); - - // Send the Worker's SendPort back to the Main Isolate - mainSendPort.send(workerReceivePort.sendPort); - - // Listen for incoming tasks - workerReceivePort.listen((message) { - print('Worker Isolate received: $message'); - - // Perform work and send result back - final result = "Processed: $message"; - mainSendPort.send(result); - }); - } - - void dispose() { - _mainReceivePort.close(); - _isolate?.kill(); - } -} -``` diff --git a/skills/flutter-handling-http-and-json/SKILL.md b/skills/flutter-handling-http-and-json/SKILL.md deleted file mode 100644 index b059181..0000000 --- a/skills/flutter-handling-http-and-json/SKILL.md +++ /dev/null @@ -1,181 +0,0 @@ ---- -name: flutter-handling-http-and-json -description: Executes HTTP requests and handles JSON serialization in a Flutter app. Use when integrating with REST APIs or parsing structured data from external sources. -metadata: - model: models/gemini-3.1-pro-preview - last_modified: Thu, 12 Mar 2026 22:18:44 GMT - ---- -# Handling HTTP and JSON - -## Contents -- [Core Guidelines](#core-guidelines) -- [Workflow: Executing HTTP Operations](#workflow-executing-http-operations) -- [Workflow: Implementing JSON Serialization](#workflow-implementing-json-serialization) -- [Workflow: Parsing Large JSON in the Background](#workflow-parsing-large-json-in-the-background) -- [Examples](#examples) - -## Core Guidelines - -- **Enforce HTTPS:** iOS and Android disable cleartext (HTTP) connections by default. Always use HTTPS endpoints. If HTTP is strictly required for debugging, configure `network_security_config.xml` (Android) and `NSAppTransportSecurity` (iOS). -- **Construct URIs Safely:** Always use `Uri.https(authority, unencodedPath, [queryParameters])` to safely build URLs. This handles encoding and formatting reliably, preventing string concatenation errors. -- **Handle Status Codes:** Always validate the `http.Response.statusCode`. Treat `200` (OK) and `201` (Created) as success. Throw explicit exceptions for other codes (do not return `null`). -- **Prevent UI Jank:** Move expensive JSON parsing operations (taking >16ms) to a background isolate using the `compute()` function. -- **Structured AI Output:** When integrating LLMs, enforce reliable JSON output by specifying a strict JSON schema in the system prompt and setting the response MIME type to `application/json`. - -## Workflow: Executing HTTP Operations - -Use this workflow to implement network requests using the `http` package. - -**Task Progress:** -- [ ] Add the `http` package to `pubspec.yaml`. -- [ ] Configure platform permissions (Internet permission in `AndroidManifest.xml` and macOS `.entitlements`). -- [ ] Construct the target `Uri`. -- [ ] Execute the HTTP method. -- [ ] Validate the response and parse the JSON payload. - -**Conditional Implementation:** -- **If fetching data (GET):** Use `http.get(uri)`. -- **If sending new data (POST):** Use `http.post(uri, headers: {...}, body: jsonEncode(data))`. Ensure `Content-Type` is `application/json; charset=UTF-8`. -- **If updating data (PUT):** Use `http.put(uri, headers: {...}, body: jsonEncode(data))`. -- **If deleting data (DELETE):** Use `http.delete(uri, headers: {...})`. - -**Feedback Loop: Validation & Error Handling** -1. Run the HTTP request. -2. Check `response.statusCode`. -3. If `200` or `201`, call `jsonDecode(response.body)` and map to a Dart object. -4. If any other code, throw an `Exception('Failed to load/update/delete resource')`. -5. Review errors -> fix endpoint, headers, or payload structure. - -## Workflow: Implementing JSON Serialization - -Choose the serialization strategy based on project complexity. - -**Conditional Implementation:** -- **If building a small prototype or simple models:** Use manual serialization with `dart:convert`. -- **If building a production app with complex/nested models:** Use code generation with `json_serializable`. - -### Manual Serialization Setup -**Task Progress:** -- [ ] Import `dart:convert`. -- [ ] Define the Model class with `final` properties. -- [ ] Implement a `factory Model.fromJson(Map json)` constructor. -- [ ] Implement a `Map toJson()` method. - -### Code Generation Setup (`json_serializable`) -**Task Progress:** -- [ ] Add dependencies: `flutter pub add json_annotation` and `flutter pub add -d build_runner json_serializable`. -- [ ] Import `json_annotation.dart` in the model file. -- [ ] Add the `part 'model_name.g.dart';` directive. -- [ ] Annotate the class with `@JsonSerializable()`. Use `explicitToJson: true` if the class contains nested models. -- [ ] Define the `fromJson` factory and `toJson` method delegating to the generated functions. -- [ ] Run the generator: `dart run build_runner build --delete-conflicting-outputs`. - -## Workflow: Parsing Large JSON in the Background - -Use this workflow to prevent frame drops when parsing large JSON payloads (e.g., lists of 1000+ items). - -**Task Progress:** -- [ ] Create a top-level or static function that takes a `String` (the response body) and returns the parsed Dart object (e.g., `List`). -- [ ] Inside the function, call `jsonDecode` and map the results to the Model class. -- [ ] In the HTTP fetch method, pass the top-level parsing function and the `response.body` to Flutter's `compute()` function. - -## Examples - -### Example 1: HTTP GET with Manual Serialization -```dart -import 'dart:convert'; -import 'package:http/http.dart' as http; - -class Album { - final int id; - final String title; - - const Album({required this.id, required this.title}); - - factory Album.fromJson(Map json) { - return Album( - id: json['id'] as int, - title: json['title'] as String, - ); - } -} - -Future fetchAlbum() async { - final uri = Uri.https('jsonplaceholder.typicode.com', '/albums/1'); - final response = await http.get(uri); - - if (response.statusCode == 200) { - return Album.fromJson(jsonDecode(response.body) as Map); - } else { - throw Exception('Failed to load album'); - } -} -``` - -### Example 2: HTTP POST Request -```dart -Future createAlbum(String title) async { - final uri = Uri.https('jsonplaceholder.typicode.com', '/albums'); - final response = await http.post( - uri, - headers: { - 'Content-Type': 'application/json; charset=UTF-8', - }, - body: jsonEncode({'title': title}), - ); - - if (response.statusCode == 201) { - return Album.fromJson(jsonDecode(response.body) as Map); - } else { - throw Exception('Failed to create album.'); - } -} -``` - -### Example 3: Background Parsing with `compute` -```dart -import 'dart:convert'; -import 'package:flutter/foundation.dart'; -import 'package:http/http.dart' as http; - -// 1. Top-level function for parsing -List parsePhotos(String responseBody) { - final parsed = (jsonDecode(responseBody) as List) - .cast>(); - return parsed.map(Photo.fromJson).toList(); -} - -// 2. Fetch function using compute -Future> fetchPhotos(http.Client client) async { - final uri = Uri.https('jsonplaceholder.typicode.com', '/photos'); - final response = await client.get(uri); - - if (response.statusCode == 200) { - // Run parsePhotos in a separate isolate - return compute(parsePhotos, response.body); - } else { - throw Exception('Failed to load photos'); - } -} -``` - -### Example 4: Code Generation (`json_serializable`) -```dart -import 'package:json_annotation/json_annotation.dart'; - -part 'user.g.dart'; - -@JsonSerializable(explicitToJson: true) -class User { - final String name; - - @JsonKey(name: 'registration_date_millis') - final int registrationDateMillis; - - User(this.name, this.registrationDateMillis); - - factory User.fromJson(Map json) => _$UserFromJson(json); - Map toJson() => _$UserToJson(this); -} -``` diff --git a/skills/flutter-implementing-navigation-and-routing/SKILL.md b/skills/flutter-implementing-navigation-and-routing/SKILL.md deleted file mode 100644 index 6fac3dc..0000000 --- a/skills/flutter-implementing-navigation-and-routing/SKILL.md +++ /dev/null @@ -1,205 +0,0 @@ ---- -name: flutter-implementing-navigation-and-routing -description: Handles routing, navigation, and deep linking in a Flutter application. Use when moving between screens or setting up URL-based navigation. -metadata: - model: models/gemini-3.1-pro-preview - last_modified: Thu, 12 Mar 2026 22:30:17 GMT - ---- -# Implementing Navigation and Routing in Flutter - -## Contents -- [Core Concepts](#core-concepts) -- [Implementing Imperative Navigation](#implementing-imperative-navigation) -- [Implementing Declarative Navigation](#implementing-declarative-navigation) -- [Implementing Nested Navigation](#implementing-nested-navigation) -- [Workflows](#workflows) -- [Examples](#examples) - -## Core Concepts - -- **Routes:** In Flutter, screens and pages are referred to as *routes*. A route is simply a widget. This is equivalent to an `Activity` in Android or a `ViewController` in iOS. -- **Navigator vs. Router:** - - Use `Navigator` (Imperative) for small applications without complex deep linking requirements. It manages a stack of `Route` objects. - - Use `Router` (Declarative) for applications with advanced navigation, web URL synchronization, and specific deep linking requirements. -- **Deep Linking:** Allows an app to open directly to a specific location based on a URL. Supported on iOS, Android, and Web. Web requires no additional setup. -- **Named Routes:** Avoid using named routes (`MaterialApp.routes` and `Navigator.pushNamed`) for most applications. They have rigid deep linking behavior and do not support the browser forward button. Use a routing package like `go_router` instead. - -## Implementing Imperative Navigation - -Use the `Navigator` widget to push and pop routes using platform-specific transition animations (`MaterialPageRoute` or `CupertinoPageRoute`). - -### Pushing and Popping -- Navigate to a new route using `Navigator.push(context, route)`. -- Return to the previous route using `Navigator.pop(context)`. -- Use `Navigator.pushReplacement()` to replace the current route, or `Navigator.pushAndRemoveUntil()` to clear the stack based on a condition. - -### Passing and Returning Data -- **Sending Data:** Pass data directly into the constructor of the destination widget. Alternatively, pass data via the `settings: RouteSettings(arguments: data)` parameter of the `PageRoute` and extract it using `ModalRoute.of(context)!.settings.arguments`. -- **Returning Data:** Pass the return value to the `pop` method: `Navigator.pop(context, resultData)`. Await the result on the pushing side: `final result = await Navigator.push(...)`. - -## Implementing Declarative Navigation - -For apps requiring deep linking, web URL support, or complex routing, implement the `Router` API via a declarative routing package like `go_router`. - -- Switch from `MaterialApp` to `MaterialApp.router`. -- Define a router configuration that parses route paths and configures the `Navigator` automatically. -- Navigate using package-specific APIs (e.g., `context.go('/path')`). -- **Page-backed vs. Pageless Routes:** Declarative routes are *page-backed* (deep-linkable). Imperative pushes (e.g., dialogs, bottom sheets) are *pageless*. Removing a page-backed route automatically removes all subsequent pageless routes. - -## Implementing Nested Navigation - -Implement nested navigation to manage a sub-flow of screens (e.g., a multi-step setup process or persistent bottom navigation tabs) independently from the top-level global navigator. - -- Instantiate a new `Navigator` widget inside the host widget. -- Assign a `GlobalKey` to the nested `Navigator` to control it programmatically. -- Implement the `onGenerateRoute` callback within the nested `Navigator` to resolve sub-routes. -- Intercept hardware back button presses using `PopScope` to prevent the top-level navigator from popping the entire nested flow prematurely. - -## Workflows - -### Workflow: Standard Screen Transition -Copy this checklist to track progress when implementing a basic screen transition: -- [ ] Create the destination widget (Route). -- [ ] Define required data parameters in the destination widget's constructor. -- [ ] Implement `Navigator.push()` in the source widget. -- [ ] Wrap the destination widget in a `MaterialPageRoute` or `CupertinoPageRoute`. -- [ ] Implement `Navigator.pop()` in the destination widget to return. - -### Workflow: Implementing Deep-Linkable Routing -Use this conditional workflow when setting up app-wide routing: -- [ ] **If** the app is simple and requires no deep linking: - - [ ] Use standard `MaterialApp` and `Navigator.push()`. -- [ ] **If** the app requires deep linking, web support, or complex flows: - - [ ] Add the `go_router` package. - - [ ] Change `MaterialApp` to `MaterialApp.router`. - - [ ] Define the `GoRouter` configuration with all top-level routes. - - [ ] Replace `Navigator.push()` with `context.go()` or `context.push()`. - -### Workflow: Creating a Nested Navigation Flow -Run this workflow when building a multi-step sub-flow (e.g., IoT device setup): -- [ ] Define string constants for the nested route paths. -- [ ] Create a `GlobalKey` in the host widget's state. -- [ ] Return a `Navigator` widget in the host's `build` method, passing the key. -- [ ] Implement `onGenerateRoute` in the nested `Navigator` to map string paths to specific step widgets. -- [ ] Wrap the host `Scaffold` in a `PopScope` to handle back-button interceptions (e.g., prompting "Are you sure you want to exit setup?"). -- [ ] Use `navigatorKey.currentState!.pushNamed()` to advance steps within the flow. - -## Examples - -### Example: Passing Data via Constructor (Imperative) - -```dart -// 1. Define the data model -class Todo { - final String title; - final String description; - const Todo(this.title, this.description); -} - -// 2. Source Screen -class TodosScreen extends StatelessWidget { - final List todos; - const TodosScreen({super.key, required this.todos}); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('Todos')), - body: ListView.builder( - itemCount: todos.length, - itemBuilder: (context, index) { - return ListTile( - title: Text(todos[index].title), - onTap: () { - // Push and pass data via constructor - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => DetailScreen(todo: todos[index]), - ), - ); - }, - ); - }, - ), - ); - } -} - -// 3. Destination Screen -class DetailScreen extends StatelessWidget { - final Todo todo; - const DetailScreen({super.key, required this.todo}); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: Text(todo.title)), - body: Padding( - padding: const EdgeInsets.all(16), - child: Text(todo.description), - ), - ); - } -} -``` - -### Example: Nested Navigation Flow - -```dart -class SetupFlow extends StatefulWidget { - final String initialRoute; - const SetupFlow({super.key, required this.initialRoute}); - - @override - State createState() => _SetupFlowState(); -} - -class _SetupFlowState extends State { - final _navigatorKey = GlobalKey(); - - void _exitSetup() => Navigator.of(context).pop(); - - @override - Widget build(BuildContext context) { - return PopScope( - canPop: false, - onPopInvokedWithResult: (didPop, _) async { - if (didPop) return; - // Intercept back button to prevent accidental exit - _exitSetup(); - }, - child: Scaffold( - appBar: AppBar(title: const Text('Setup')), - body: Navigator( - key: _navigatorKey, - initialRoute: widget.initialRoute, - onGenerateRoute: _onGenerateRoute, - ), - ), - ); - } - - Route _onGenerateRoute(RouteSettings settings) { - Widget page; - switch (settings.name) { - case 'step1': - page = StepOnePage( - onComplete: () => _navigatorKey.currentState!.pushNamed('step2'), - ); - break; - case 'step2': - page = StepTwoPage(onComplete: _exitSetup); - break; - default: - throw StateError('Unexpected route name: ${settings.name}!'); - } - - return MaterialPageRoute( - builder: (context) => page, - settings: settings, - ); - } -} -``` diff --git a/skills/flutter-improving-accessibility/SKILL.md b/skills/flutter-improving-accessibility/SKILL.md deleted file mode 100644 index b3e044b..0000000 --- a/skills/flutter-improving-accessibility/SKILL.md +++ /dev/null @@ -1,110 +0,0 @@ ---- -name: flutter-improving-accessibility -description: Configures a Flutter app to support assistive technologies like Screen Readers. Use when ensuring an application is usable for people with disabilities. -metadata: - model: models/gemini-3.1-pro-preview - last_modified: Thu, 12 Mar 2026 22:17:37 GMT - ---- -# Implementing Flutter Accessibility - -## Contents -- [UI Design and Styling](#ui-design-and-styling) -- [Accessibility Widgets](#accessibility-widgets) -- [Web Accessibility](#web-accessibility) -- [Adaptive and Responsive Design](#adaptive-and-responsive-design) -- [Workflows](#workflows) -- [Examples](#examples) - -## UI Design and Styling -Design layouts to accommodate dynamic scaling and high visibility. Flutter automatically calculates font sizes based on OS-level accessibility settings. - -* **Font Scaling:** Ensure layouts provide sufficient room to render all contents when font sizes are increased to their maximum OS settings. Avoid hardcoding fixed heights on text containers. -* **Color Contrast:** Maintain a contrast ratio of at least 4.5:1 for small text and 3.0:1 for large text (18pt+ regular or 14pt+ bold) to meet W3C standards. -* **Tap Targets:** Enforce a minimum tap target size of 48x48 logical pixels to accommodate users with limited dexterity. - -## Accessibility Widgets -Utilize Flutter's catalog of accessibility widgets to manipulate the semantics tree exposed to assistive technologies (like TalkBack or VoiceOver). - -* **`Semantics`**: Use this to annotate the widget tree with a description of the meaning of the widgets. Assign specific roles using the `SemanticsRole` enum (e.g., button, link, heading) when building custom components. -* **`MergeSemantics`**: Wrap composite widgets to merge the semantics of all descendants into a single selectable node for screen readers. -* **`ExcludeSemantics`**: Use this to drop the semantics of all descendants, hiding redundant or purely decorative sub-widgets from accessibility tools. - -## Web Accessibility -Flutter web renders UI on a single canvas, requiring a specialized DOM layer to expose structure to browsers. - -* **Enable Semantics:** Web accessibility is disabled by default for performance. Users can enable it via an invisible button (`aria-label="Enable accessibility"`). -* **Programmatic Enablement:** If building a web-first application requiring default accessibility, force the semantics tree generation at startup. -* **Semantic Roles:** Rely on standard widgets (`TabBar`, `MenuAnchor`, `Table`) for automatic ARIA role mapping. For custom components, explicitly assign `SemanticsRole` values to ensure screen readers interpret the elements correctly. - -## Adaptive and Responsive Design -Differentiate between adaptive and responsive paradigms to build universal applications. - -* **Responsive Design:** Adjust the placement, sizing, and reflowing of design elements to fit the available screen space. -* **Adaptive Design:** Select appropriate layouts (e.g., bottom navigation vs. side panel) and input mechanisms (e.g., touch vs. mouse/keyboard) to make the UI usable within the current device context. Design to the strengths of each form factor. - -## Workflows - -### Task Progress: Accessibility Implementation -Copy this checklist to track accessibility compliance during UI development: - -- [ ] Verify all interactive elements have a minimum tap target of 48x48 pixels. -- [ ] Test layout with maximum OS font size settings to ensure no text clipping or overflow occurs. -- [ ] Validate color contrast ratios (4.5:1 for normal text, 3.0:1 for large text). -- [ ] Wrap custom interactive widgets in `Semantics` and assign the appropriate `SemanticsRole`. -- [ ] Group complex composite widgets using `MergeSemantics` to prevent screen reader fatigue. -- [ ] Hide decorative elements from screen readers using `ExcludeSemantics`. -- [ ] If targeting web, verify ARIA roles are correctly mapped and consider programmatic enablement of the semantics tree. - -### Feedback Loop: Accessibility Validation -Run this loop when finalizing a view or component: -1. **Run validator:** Execute accessibility tests or use OS-level screen readers (VoiceOver/TalkBack) to navigate the view. -2. **Review errors:** Identify unannounced interactive elements, trapped focus, or clipped text. -3. **Fix:** Apply `Semantics`, adjust constraints, or modify colors. Repeat until the screen reader provides a clear, logical traversal of the UI. - -## Examples - -### Programmatic Web Accessibility Enablement -If targeting web and requiring accessibility by default, initialize the semantics binding before running the app. - -```dart -import 'package:flutter/material.dart'; -import 'package:flutter/semantics.dart'; -import 'package:flutter/foundation.dart'; - -void main() { - if (kIsWeb) { - SemanticsBinding.instance.ensureSemantics(); - } - runApp(const MyApp()); -} -``` - -### Custom Component Semantics -If building a custom widget that acts as a list item, explicitly define its semantic role so assistive technologies and web ARIA mappings interpret it correctly. - -```dart -import 'package:flutter/material.dart'; -import 'package:flutter/semantics.dart'; - -class CustomListItem extends StatelessWidget { - final String text; - - const CustomListItem({super.key, required this.text}); - - @override - Widget build(BuildContext context) { - return Semantics( - role: SemanticsRole.listItem, - label: text, - child: Padding( - padding: const EdgeInsets.all(12.0), // Ensures > 48px tap target if interactive - child: Text( - text, - style: const TextStyle(fontSize: 16), // Ensure contrast ratio > 4.5:1 - ), - ), - ); - } -} -``` diff --git a/skills/flutter-interoperating-with-native-apis/SKILL.md b/skills/flutter-interoperating-with-native-apis/SKILL.md deleted file mode 100644 index 3b38c48..0000000 --- a/skills/flutter-interoperating-with-native-apis/SKILL.md +++ /dev/null @@ -1,127 +0,0 @@ ---- -name: flutter-interoperating-with-native-apis -description: Interoperates with native platform APIs on Android, iOS, and the web. Use when accessing device-specific features not available in Dart or calling existing native code. -metadata: - model: models/gemini-3.1-pro-preview - last_modified: Thu, 12 Mar 2026 22:21:02 GMT - ---- -# Integrating Platform-Specific Code in Flutter - -## Contents -- [Core Concepts & Terminology](#core-concepts--terminology) -- [Binding to Native C/C++ Code (FFI)](#binding-to-native-cc-code-ffi) -- [Implementing Platform Channels & Pigeon](#implementing-platform-channels--pigeon) -- [Hosting Native Platform Views](#hosting-native-platform-views) -- [Integrating Web Content & Wasm](#integrating-web-content--wasm) -- [Workflows](#workflows) - -## Core Concepts & Terminology -- **FFI (Foreign Function Interface):** The `dart:ffi` library used to bind Dart directly to native C/C++ APIs. -- **Platform Channel:** The asynchronous message-passing system (`MethodChannel`, `BasicMessageChannel`) connecting the Dart client (UI) to the host platform (Kotlin/Java, Swift/Objective-C, C++). -- **Pigeon:** A code-generation tool that creates type-safe Platform Channels. -- **Platform View:** A mechanism to embed native UI components (e.g., Android `View`, iOS `UIView`) directly into the Flutter widget tree. -- **JS Interop:** The modern, Wasm-compatible approach to interacting with JavaScript and DOM APIs using `package:web` and `dart:js_interop`. - -## Binding to Native C/C++ Code (FFI) -Use FFI to execute high-performance native code or utilize existing C/C++ libraries without the overhead of asynchronous Platform Channels. - -### Project Setup -* **If creating a standard C/C++ integration (Recommended since Flutter 3.38):** Use the `package_ffi` template. This utilizes `build.dart` hooks to compile native code, eliminating the need for OS-specific build files (CMake, build.gradle, podspec). - ```bash - flutter create --template=package_ffi - ``` -* **If requiring access to the Flutter Plugin API or Play Services:** Use the legacy `plugin_ffi` template. - ```bash - flutter create --template=plugin_ffi - ``` - -### Implementation Rules -* **Symbol Visibility:** Always mark C++ symbols with `extern "C"` and prevent linker discarding during link-time optimization (LTO). - ```cpp - extern "C" __attribute__((visibility("default"))) __attribute__((used)) - ``` -* **Dynamic Library Naming (Apple Platforms):** Ensure your `build.dart` hook produces the exact same filename across all target architectures (e.g., `arm64` vs `x86_64`) and SDKs (`iphoneos` vs `iphonesimulator`). Do not append architecture suffixes to the `.dylib` or `.framework` names. -* **Binding Generation:** Always use `package:ffigen` to generate Dart bindings from your C headers (`.h`). Configure this in `ffigen.yaml`. - -## Implementing Platform Channels & Pigeon -Use Platform Channels when you need to interact with platform-specific APIs (e.g., Battery, Bluetooth, OS-level services) using the platform's native language. - -### Pigeon (Type-Safe Channels) -Always prefer `package:pigeon` over raw `MethodChannel` implementations for complex or frequently used APIs. -1. Define the messaging protocol in a standalone Dart file using Pigeon annotations (`@HostApi()`). -2. Generate the host (Kotlin/Swift/C++) and client (Dart) code. -3. Implement the generated interfaces on the native side. - -### Threading Rules -* **Main Thread Requirement:** Always invoke channel methods destined for Flutter on the platform's main thread (UI thread). -* **Background Execution:** If executing channel handlers on a background thread (Android/iOS), you must use the Task Queue API (`makeBackgroundTaskQueue()`). -* **Isolates:** To use plugins/channels from a Dart background `Isolate`, ensure it is registered using `BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken)`. - -## Hosting Native Platform Views -Use Platform Views to embed native UI components (e.g., Google Maps, native video players) into the Flutter widget tree. - -### Android Platform Views -Evaluate the trade-offs between the two rendering modes and select the appropriate one: -* **If requiring perfect fidelity, accessibility, or SurfaceView support:** Use **Hybrid Composition** (`PlatformViewLink` + `AndroidViewSurface`). This appends the native view to the hierarchy but may reduce Flutter's rendering performance. -* **If prioritizing Flutter rendering performance and transformations:** Use **Texture Layer** (`AndroidView`). This renders the native view into a texture. Note: Quick scrolling may drop frames, and `SurfaceView` is problematic. - -### iOS Platform Views -* iOS exclusively uses Hybrid Composition. -* Implement `FlutterPlatformViewFactory` and `FlutterPlatformView` in Swift or Objective-C. -* Use the `UiKitView` widget on the Dart side. -* *Limitation:* `ShaderMask` and `ColorFiltered` widgets cannot be applied to iOS Platform Views. - -## Integrating Web Content & Wasm -Flutter Web supports compiling to WebAssembly (Wasm) for improved performance and multi-threading. - -### Wasm Compilation -* Compile to Wasm using: `flutter build web --wasm`. -* **Server Configuration:** To enable multi-threading, configure your HTTP server to emit the following headers: - * `Cross-Origin-Embedder-Policy: credentialless` (or `require-corp`) - * `Cross-Origin-Opener-Policy: same-origin` -* *Limitation:* WasmGC is not currently supported on iOS browsers (WebKit limitation). Flutter will automatically fall back to JavaScript if WasmGC is unavailable. - -### Web Interop -* **If writing new web-specific code:** Strictly use `package:web` and `dart:js_interop`. -* **Do NOT use:** `dart:html`, `dart:js`, or `package:js`. These are incompatible with Wasm compilation. -* **Embedding HTML:** Use `HtmlElementView.fromTagName` to inject arbitrary HTML elements (like `