diff --git a/README.md b/README.md index b8a74bf..6c72bae 100644 --- a/README.md +++ b/README.md @@ -24,28 +24,17 @@ npx skills update flutter/skills | Skill | Description | |---|---| -| [flutter-adding-home-screen-widgets](skills/flutter-adding-home-screen-widgets/SKILL.md) | 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. | -| [flutter-animating-apps](skills/flutter-animating-apps/SKILL.md) | Implements animated effects, transitions, and motion in a Flutter app. Use when adding visual feedback, shared element transitions, or physics-based animations. | -| [flutter-architecting-apps](skills/flutter-architecting-apps/SKILL.md) | Architects a Flutter application using the recommended layered approach (UI, Logic, Data). Use when structuring a new project or refactoring for scalability. | -| [flutter-building-forms](skills/flutter-building-forms/SKILL.md) | Builds Flutter forms with validation and user input handling. Use when creating login screens, data entry forms, or any multi-field user input. | -| [flutter-building-layouts](skills/flutter-building-layouts/SKILL.md) | Builds Flutter layouts using the constraint system and layout widgets. Use when creating or refining the UI structure of a Flutter application. | -| [flutter-building-plugins](skills/flutter-building-plugins/SKILL.md) | Builds Flutter plugins that provide native interop for other apps to use. Use when creating reusable packages that bridge Flutter with platform-specific functionality. | -| [flutter-caching-data](skills/flutter-caching-data/SKILL.md) | 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. | -| [flutter-embedding-native-views](skills/flutter-embedding-native-views/SKILL.md) | Embeds native Android, iOS, or macOS views into a Flutter app. Use when integrating complex native components like maps or web views. | -| [flutter-handling-concurrency](skills/flutter-handling-concurrency/SKILL.md) | Executes long-running tasks in background isolates to keep the UI responsive. Use when performing heavy computations or parsing large datasets. | -| [flutter-handling-http-and-json](skills/flutter-handling-http-and-json/SKILL.md) | Executes HTTP requests and handles JSON serialization in a Flutter app. Use when integrating with REST APIs or parsing structured data from external sources. | -| [flutter-implementing-navigation-and-routing](skills/flutter-implementing-navigation-and-routing/SKILL.md) | Handles routing, navigation, and deep linking in a Flutter application. Use when moving between screens or setting up URL-based navigation. | -| [flutter-improving-accessibility](skills/flutter-improving-accessibility/SKILL.md) | Configures a Flutter app to support assistive technologies like Screen Readers. Use when ensuring an application is usable for people with disabilities. | -| [flutter-interoperating-with-native-apis](skills/flutter-interoperating-with-native-apis/SKILL.md) | 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. | -| [flutter-localizing-apps](skills/flutter-localizing-apps/SKILL.md) | Configures a Flutter app to support multiple languages and regions. Use when preparing an application for international markets and diverse user locales. | -| [flutter-managing-state](skills/flutter-managing-state/SKILL.md) | Manages application and ephemeral state in a Flutter app. Use when sharing data between widgets or handling complex UI state transitions. | -| [flutter-reducing-app-size](skills/flutter-reducing-app-size/SKILL.md) | Measures and optimizes the size of Flutter application bundles for deployment. Use when minimizing download size or meeting app store package constraints. | -| [flutter-setting-up-on-linux](skills/flutter-setting-up-on-linux/SKILL.md) | Sets up a Linux environment for Flutter development. Use when configuring a Linux machine to run, build, or deploy Flutter applications. | -| [flutter-setting-up-on-macos](skills/flutter-setting-up-on-macos/SKILL.md) | Sets up a macOS environment for Flutter development. Use when configuring a macOS machine to run, build, or deploy Flutter applications for iOS or macOS. | -| [flutter-setting-up-on-windows](skills/flutter-setting-up-on-windows/SKILL.md) | Sets up a Windows environment for Flutter development. Use when configuring a Windows machine to run, build, or deploy Flutter applications for Windows desktop or Android. | -| [flutter-testing-apps](skills/flutter-testing-apps/SKILL.md) | Implements unit, widget, and integration tests for a Flutter app. Use when ensuring code quality and preventing regressions through automated testing. | -| [flutter-theming-apps](skills/flutter-theming-apps/SKILL.md) | Customizes the visual appearance of a Flutter app using the theming system. Use when defining global styles, colors, or typography for an application. | -| [flutter-working-with-databases](skills/flutter-working-with-databases/SKILL.md) | Manages local data persistence using SQLite or other database solutions. Use when a Flutter app needs to store, query, or synchronize large amounts of structured data on the device. | +| [flutter-accessibility-audit](skills/flutter-accessibility-audit/SKILL.md) | Triggers an accessibility scan through the widget_inspector and automatically adds Semantics widgets or missing labels to the source code. | +| [flutter-add-integration-test](skills/flutter-add-integration-test/SKILL.md) | Configures Flutter Driver for app interaction and converts MCP actions into permanent integration tests. Use when adding integration testing to a project, exploring UI components via MCP, or automating user flows with the integration_test package. | +| [flutter-add-widget-preview](skills/flutter-add-widget-preview/SKILL.md) | Adds interactive widget previews to the project using the previews.dart system. Use when creating new UI components or updating existing screens to ensure consistent design and interactive testing. | +| [flutter-add-widget-test](skills/flutter-add-widget-test/SKILL.md) | Implement a component-level test using `WidgetTester` to verify UI rendering and user interactions (tapping, scrolling, entering text). Use when validating that a specific widget displays correct data and responds to events as expected. | +| [flutter-apply-architecture-best-practices](skills/flutter-apply-architecture-best-practices/SKILL.md) | Architects a Flutter application using the recommended layered approach (UI, Logic, Data). Use when structuring a new project or refactoring for scalability. | +| [flutter-build-responsive-layout](skills/flutter-build-responsive-layout/SKILL.md) | Use `LayoutBuilder`, `MediaQuery`, or `Expanded/Flexible` to create a layout that adapts to different screen sizes. Use when you need the UI to look good on both mobile and tablet/desktop form factors. | +| [flutter-fix-layout-issues](skills/flutter-fix-layout-issues/SKILL.md) | Fixes Flutter layout errors (overflows, unbounded constraints) using Dart and Flutter MCP tools. Use when addressing "RenderFlex overflowed", "Vertical viewport was given unbounded height", or similar layout issues. | +| [flutter-implement-json-serialization](skills/flutter-implement-json-serialization/SKILL.md) | Create model classes with `fromJson` and `toJson` methods using `dart:convert`. Use when manually mapping JSON keys to class properties for simple data structures. | +| [flutter-setup-declarative-routing](skills/flutter-setup-declarative-routing/SKILL.md) | Configure `MaterialApp.router` using a package like `go_router` for advanced URL-based navigation. Use when developing web applications or mobile apps that require specific deep linking and browser history support. | +| [flutter-setup-localization](skills/flutter-setup-localization/SKILL.md) | Add `flutter_localizations` and `intl` dependencies, enable "generate true" in `pubspec.yaml`, and create an `l10n.yaml` configuration file. Use when initializing localization support for a new Flutter project. | +| [flutter-use-http-package](skills/flutter-use-http-package/SKILL.md) | Use the `http` package to execute GET, POST, PUT, or DELETE requests. Use when you need to fetch from or send data to a REST API. | ## Contributing To contribute skills, see the instructions in [tool/generator/README.md](tool/generator/README.md). diff --git a/resources/flutter_skills.yaml b/resources/flutter_skills.yaml index 57140c9..14acbbc 100644 --- a/resources/flutter_skills.yaml +++ b/resources/flutter_skills.yaml @@ -6,1439 +6,452 @@ # To generate skills, use the following command (from the `tool` directory): # dart run skills generate-skill --config ../resources/flutter_skills.yaml --output ../skills -- name: flutter-setting-up-on-linux - description: Sets up a Linux environment for Flutter development. Use when configuring a Linux machine to run, build, or deploy Flutter applications. +- name: flutter-add-integration-test + description: Configures Flutter Driver for app interaction and converts MCP + actions into permanent integration tests. Use when adding integration + testing to a project, exploring UI components via MCP, or automating user + flows with the integration_test package. instructions: | - Setting up a Linux environment for app development involves configuring your - system to run, build, and deploy applications. This often includes - installing necessary tools and prerequisites. - - For Flutter app development on Linux, you need to configure your development - environment to build Flutter apps for the Linux desktop. If you haven't - already, you should first set up Flutter by following the "Get started with - Flutter" guide, and ensure it's up to date. To run and debug desktop Flutter - apps on Linux, you'll need to download and install prerequisite packages. - These prerequisites can include `curl`, `git`, `unzip`, `xz-utils`, `zip`, - and `libglu1-mesa`. On Debian-based distributions like Ubuntu, these - packages can be installed using `sudo apt-get update -y && sudo apt-get - upgrade -y` followed by `sudo apt-get install -y curl git unzip xz-utils zip - libglu1-mesa`. For the best experience, consider installing an editor or IDE - with Flutter support. If you are developing on a Chromebook, you need to - turn on Linux support and ensure it's up to date. + 1. **Project Setup and Dependency Management:** + * Add the `integration_test` package as a `dev_dependency` to your `pubspec.yaml` file. This can be done by running `flutter pub add 'dev:integration_test:{"sdk":"flutter"}'`. + * Ensure the `flutter_test` package is also included as a `dev_dependency`. + * Ensure `flutter_driver` is configured by importing `package:flutter_driver/driver_extension.dart' and calling `enableFlutterDriverExtension()` + + 2. **Interactive Exploration (Flutter Driver MCP)** + * Once configured, use the `flutter-driver` MCP tools to explore the app: + 1. **Launch**: Use `launch_app` with `target: "lib/main_test.dart"`. + 2. **Inspect**: Use `get_widget_tree` to find `Key`s, `Text`, or `Type`s. + 3. **Interact**: Use `tap`, `enter_text`, and `scroll` to execute your flow. + 4. **Wait**: Always call `waitFor` or check `get_health` if transitions are slow. + * If a widget isn't found, it might be unmounted in a `SliverList` or `ListView`. Use `scroll` or `scrollIntoView` first. + 3. **Integration Test File Creation:** + * Create a new directory named `integration_test/` within your project. + * Inside this directory, create your test files with the format `_test.dart`. + * For running tests with `flutter drive`, create a `test_driver` directory and an `integration_test.dart` file within it, containing `integrationDriver()`. + + 4. **Writing Integration Tests:** + * Integration tests verify the behavior of the complete app and can be used to validate how individual pieces work together or capture app performance on a real device. They are also known as end-to-end or GUI testing. + * Use `flutter_test` APIs to write tests in a style similar to widget tests. + * To verify a widget is displayed, load the main app widget using `tester.pumpWidget` and then use `expect` with `findsOneWidget`. + * For user interactions like tapping, use `tester.tap` and then `tester.pumpAndSettle` to wait for UI changes. + * To verify a widget is not displayed, use `expect` with `findsNothing`. + * For scrolling, `integration_test` allows using `tester.scrollUntilVisible` to scroll to a specific item. + * When working with `flutter_driver` (for migration or specific scenarios), `waitFor` is used to locate widgets, and `waitForAbsent` to ensure a widget is not present. `flutter_driver` also uses `driver.tap` for interactions and `driver.scroll` for scrolling. + * Add `Key` parameters to widgets, such as `ValueKey('increment')`, to allow finding specific widgets within the test suite. + + 5. **Running Integration Tests:** + * Integration tests can be run on a physical device or emulator using the `flutter drive` command. + * To run tests on Chrome, launch `chromedriver` and then use `flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart -d chrome`. + * To run as a headless test, use `flutter drive` with the `-d web-server` option. + * For Android, you can build a debug APK using `flutter build apk --debug` and then build an Android test APK using `./gradlew app:assembleAndroidTest`. + * Integration tests can also be run on Firebase Test Lab to automate testing on various devices. This involves creating an APK using Gradle and uploading it to the Firebase Test Lab Console. resources: - - https://docs.flutter.dev/platform-integration/linux/setup - - https://docs.flutter.dev/platform-integration/android/setup - - https://docs.flutter.dev/install - - https://docs.flutter.dev/deployment/linux - - https://docs.flutter.dev/platform-integration/linux - - https://docs.flutter.dev/install/manual - - https://docs.flutter.dev/install/custom - - https://docs.flutter.dev/platform-integration/linux/building - - https://docs.flutter.dev/learn/pathway/quick-install -- name: flutter-setting-up-on-macos - description: Sets up a macOS environment for Flutter development. Use when configuring a macOS machine to run, build, or deploy Flutter applications for iOS or macOS. + - https://raw.githubusercontent.com/dart-lang/ai/refs/heads/main/pkgs/dart_mcp_server/README.md + - https://docs.flutter.dev/cookbook/testing/integration + - https://docs.flutter.dev/cookbook/testing/integration/introduction + - https://docs.flutter.dev/cookbook/testing/integration/profiling + - https://docs.flutter.dev/testing/integration-tests + - https://docs.flutter.dev/testing/plugins-in-tests + - https://docs.flutter.dev/testing/testing-plugins +- name: flutter-fix-layout-issues + description: Fixes Flutter layout errors (overflows, unbounded constraints) + using Dart and Flutter MCP tools. Use when addressing "RenderFlex overflowed", + "Vertical viewport was given unbounded height", or similar layout issues. instructions: | - To set up a macOS environment for Flutter development, you need to configure - your development environment to run, build, and deploy Flutter applications - for macOS devices. This involves setting up tooling, validating your setup, - and then you can start developing for macOS. - - First, ensure Flutter is installed and up to date. If you haven't installed - Flutter yet, you should follow the "Get started with Flutter" guide. - - For tooling, you will need Xcode. Xcode allows you to run Flutter apps on - macOS and compile and debug native Swift and Objective-C code. Install the - latest version of Xcode, or update it if you already have it. - - After setting up the tooling, validate your setup by running the `flutter - doctor` command in your terminal. This command checks for any issues with - your macOS development setup. If there are errors or tasks under the Xcode - section, resolve them and run `flutter doctor -v` again to verify the - changes. You should also run `flutter devices` to ensure Flutter can find - and connect to your macOS device. If everything is set up correctly, you - should see at least one entry with "macos" as the platform. If you encounter - any setup issues, you can refer to the "Install and setup troubleshooting" - guide or reach out to the Flutter community channels. - - Once your macOS desktop development for Flutter is set up, you can continue - your Flutter learning journey, test on macOS, or begin expanding integration - with macOS. You can also build and deploy to macOS, bind to native macOS - code, embed native macOS views, set up app flavors, and use Swift Package - Manager. Flutter supports building macOS apps. You can also host a native - macOS view and bind to native code. + 1. **Identify the Layout Error:** + * Recognize common Flutter framework errors, including layout errors. + * Look for specific error messages such as "RenderFlex overflowed", "Vertical viewport was given unbounded height", "An InputDecorator...cannot have an unbounded width", or "RenderBox was not laid out". + * A solid red or grey screen can also indicate an error. + + 2. **Understand the Cause of the Error:** + * **"Vertical viewport was given unbounded height"**: This error often occurs when a scrollable widget, like a `ListView` or `GridView`, is placed inside a `Column`. `ListView`s take all available vertical space unless constrained by a parent, and `Column`s don't impose height constraints on their children by default, leading to an inability to determine the `ListView`'s size. + * **Unbounded Width Errors (e.g., `InputDecorator` or `TextField`)**: This happens when a `Row` contains a `TextFormField` or `TextField` without a width constraint. Similarly, a `Column` might try to be wider than its parent `Row` can allocate, causing an overflow. This is due to Flutter's layout process where parents pass down constraints, and children respond by passing up a size within those constraints. If a child, like a `Text` widget, determines its own width based on its content, and its parent (`Column`) adopts that width, it can clash with the parent's (`Row`) maximum horizontal space. + * **"RenderBox was not laid out"**: This error is often a side effect of a primary error earlier in the rendering pipeline. It usually relates to violations of box constraints, meaning Flutter needs more information on how to constrain the widgets. + * **"Incorrect use of ParentData widget"**: This error indicates that a `ParentDataWidget` is not placed directly inside a compatible ancestor widget. Certain widgets, like `Flexible` and `Expanded`, expect specific parent widgets such as `Row`, `Column`, or `Flex`. `Positioned` expects a `Stack`, and `TableCell` expects a `Table`. + + 3. **Apply the Appropriate Fix:** + * **For "Vertical viewport was given unbounded height" (ListView in Column)**: + * Specify the height of the `ListView`. + * Wrap the `ListView` in an `Expanded` widget to make it take the remaining space in the `Column`. + * Alternatively, use a `SizedBox` for an absolute height or a `Flexible` widget for a relative height. + * **For unbounded width errors (e.g., `TextField` in `Row` or `Column` in `Row`)**: + * Constrain the width of the widget (e.g., `InputDecorator`, `TextField`, or `Column`). + * Wrap the widget in an `Expanded` widget. + * Wrap the widget in a `Flexible` widget and specify a flex factor. An `Expanded` widget is equivalent to a `Flexible` widget with a flex factor of 1.0. + * **For "RenderBox was not laid out"**: The solution involves providing more information to Flutter about how to constrain the widgets. Understanding how constraints work in Flutter is crucial. + * **For "Incorrect use of ParentData widget"**: The fix is to place the `ParentDataWidget` inside its expected parent widget. + + 4. **Review and Refine:** + * Compare your code to working examples if available. + * Use automatic reformatting support in your Flutter editor. + * Leverage Flutter's hot reload feature to accelerate development and see changes quickly. resources: - - https://docs.flutter.dev/platform-integration/macos/setup - - https://docs.flutter.dev/install/add-to-path - - https://docs.flutter.dev/install/troubleshoot - - https://docs.flutter.dev/install - - https://docs.flutter.dev/install/quick - - https://docs.flutter.dev/platform-integration/ios/setup - - https://docs.flutter.dev/platform-integration/macos -- name: flutter-setting-up-on-windows - description: Sets up a Windows environment for Flutter development. Use when configuring a Windows machine to run, build, or deploy Flutter applications for Windows desktop or Android. + - https://raw.githubusercontent.com/dart-lang/ai/refs/heads/main/pkgs/dart_mcp_server/README.md + - https://docs.flutter.dev/get-started/fundamentals/layout + - https://docs.flutter.dev/release/breaking-changes/layout-builder-optimization + - https://docs.flutter.dev/testing/common-errors + - https://docs.flutter.dev/testing/errors + - https://docs.flutter.dev/tools/devtools/inspector + - https://docs.flutter.dev/tools/devtools/legacy-inspector + - https://docs.flutter.dev/tools/flutter-fix + - https://docs.flutter.dev/ui/layout + - https://docs.flutter.dev/ui/layout/constraints + - https://docs.flutter.dev/ui/layout/tutorial +- name: flutter-add-widget-preview + description: Adds interactive widget previews to the project using the + previews.dart system. Use when creating new UI components or updating + existing screens to ensure consistent design and interactive testing. instructions: | - To set up a Windows environment for app development, particularly with - Flutter, several steps are involved. - - First, you need to install Flutter itself. If you haven't downloaded - Flutter, you should follow the "Get started with Flutter" guide. If Flutter - is already installed, ensure it's up to date. After downloading the Flutter - SDK, you must add its `bin` directory to your system's PATH environment - variable to use `flutter` and `dart` command-line tools in terminals and - IDEs. On Windows, this involves copying the absolute path to the Flutter - SDK's directory and navigating to the environment variables settings. - - Next, you'll need to set up tooling. For Windows development with Flutter, - Visual Studio is required. Visual Studio allows you to run Flutter apps on - Windows and compile and debug native C and C++ code. It's important to note - that Visual Studio is a separate IDE from Visual Studio Code and is only - supported on Windows. If you haven't installed Visual Studio, follow - Microsoft's guide; otherwise, update it to the latest version. - - You can also set up Flutter support in various IDEs and editors, such as VS - Code, Android Studio, IntelliJ, and Firebase Studio. - - After installing Flutter and setting up tooling, you need to set up - development for at least one target platform. While web development is - recommended first as it requires no additional setup beyond a browser, you - can set up development for other platforms later. For Windows desktop - development, you specifically configure your environment to run, build, and - deploy Flutter apps for the Windows desktop platform. + 1. Defining Widget Previews: + * To preview a widget, use the `@Preview` annotation defined in `package:flutter/widget_previews.dart`. + * This annotation can be applied to top-level functions that return a `Widget` or `WidgetBuilder`, static methods within a class that return a `Widget` or `WidgetBuilder`, or public `Widget` constructors and factories with no required arguments. + * A basic example involves importing `package:flutter/widget_previews.dart` and `package:flutter/material.dart`, then defining a function annotated with `@Preview` that returns a `Widget`, such as a `Text` widget. For instance: + ```dart + import 'package:flutter/widget_previews.dart'; + import 'package:flutter/material.dart'; + + @Preview(name: 'My Sample Text') + Widget mySampleText() { + return const Text('Hello, World!'); + } + ``` - If you are developing for Android on Windows, you'll need to configure your - development environment to run, build, and deploy Flutter apps for Android - devices. This includes setting up Android tooling and an Android device. For - physical Android devices, you need to enable Developer options and USB - debugging, and install the necessary USB driver for your device. You can - also enable wireless debugging. For emulated performance, you should enable - hardware acceleration by selecting an option that mentions "Hardware" in the - Graphics acceleration dropdown menu under "Emulated Performance" in - Additional settings. After configuring, you can verify your virtual device - and run the emulator. - - To build a zip file for Windows, you'll find the Flutter executable (`.exe`) - in your project under `build\windows\runner\\`. In addition to - the executable, you'll need all the `.dll` files from the same directory and - the `data` directory. The Visual C++ redistributables are also necessary, - and you can use methods from Microsoft's deployment example walkthroughs to - ensure end-users have them. - - For certain functionalities, like generating certificates, you might need - OpenSSL. You can set an environment variable for OpenSSL and then use - commands to generate a private key, a certificate signing request (CSR) - file, a signed certificate (CRT) file, and a `.pfx` file. The `.pfx` - certificate needs to be installed on the local machine in the Certificate - store as "Trusted Root Certification Authorities" before installing the app. - - After enabling desktop support, you should restart your IDE so it can detect - the new device. When creating a new project with desktop support, it will - automatically create iOS, Android, web, and desktop versions of your app. If - `flutter doctor` finds problems for platforms you don't intend to develop - for, you can ignore the warnings or disable the platform using `flutter - config` commands, such as `--no-enable-windows-desktop`. + * Multiple preview configurations can be created by applying multiple `@Preview` annotations to a single function or constructor. + * For common configurations, extend `MultiPreview` to create a custom annotation. `MultiPreview` also provides a `transform()` method to perform transformations on each preview at runtime. + + 2. Interacting with Previews: + * As of Flutter 3.38, Android Studio, IntelliJ, and Visual Studio Code automatically start the Flutter Widget Previewer on launch. + * Alternatively, the previewer can be started from the command line by navigating to the Flutter project's root directory and running `flutter widget-preview start`. This command launches a local server and opens a Widget Preview environment in Chrome that updates automatically with project changes. resources: - - https://docs.flutter.dev/install - - https://docs.flutter.dev/install/add-to-path - - https://docs.flutter.dev/install/manual - - https://docs.flutter.dev/install/custom - - https://docs.flutter.dev/install/troubleshoot - - https://docs.flutter.dev/platform-integration/windows/building - - https://docs.flutter.dev/platform-integration/windows/setup - - https://docs.flutter.dev/tools/vs-code -- 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. + - https://docs.flutter.dev/tools/widget-previewer +- name: flutter-apply-architecture-best-practices + description: Architects a Flutter application using the recommended layered + approach (UI, Logic, Data). Use when structuring a new project or + refactoring for scalability. instructions: | - Architecture is a broad term that can refer to many topics depending on the - context. In the context of Flutter, architecture refers to how to structure, - organize, and design your Flutter app to scale as project requirements and - teams grow. It is an important part of building a maintainable, resilient, - and scalable Flutter app. This guide covers app architecture principles and - best practices for building Flutter apps. - - Good app architecture provides several benefits to engineering teams and end - users. It makes it easier to modify, update, and fix issues over time - (maintainability). A well-thought-out application allows more people to - contribute to the same codebase concurrently with minimal code conflicts - (scalability). - - Common architectural principles include separation of concerns, layered - architecture, single source of truth, unidirectional data flow, UI as a - function of (immutable) state, extensibility, and testability. - - Flutter applications should be written in layers. Layered architecture is a - software design pattern that organizes an application into distinct layers, - each with specific roles and responsibilities. Typically, applications are - separated into 2 to 3 layers, depending on complexity. The three common - layers of app architecture are the UI layer, logic layer, and data layer. + **1. Understand the Core Principles and Layers:** + * **Separation of Concerns** is the most important principle to follow when designing your Flutter app. This principle makes your code less error-prone by keeping widgets "dumb". + * Flutter applications should be written in **layers**. Layered architecture organizes an application into distinct layers, each with specific roles and responsibilities. + * Typically, applications are separated into 2 to 3 layers, depending on complexity. The three common layers are the UI layer, logic layer, and data layer. + * The Flutter team strongly recommends using clearly defined **data and UI layers**. + * Within each layer, further separate your application by **feature or functionality**. For example, authentication logic should be in a different class than search logic. + + **2. Structure the UI Layer:** + * The **UI layer** displays data to the user that is exposed by the business logic layer, and handles user interaction. It is also commonly referred to as the 'presentation layer'. + * The UI layer contains separate classes for UI logic and widgets. + * Use **ViewModels and Views** in the UI layer. Views and ViewModels make up the UI layer of an application. + * Implement the **MVVM architectural pattern** (Model-View-ViewModel) in the UI layer. MVVM separates a feature into three parts: Model, ViewModel, and View. + * Write **reusable, lean widgets** that hold as little logic as possible. Do not put logic in widgets. + * Use **ChangeNotifiers and Listenables** to handle widget updates. The ChangeNotifier API is part of the Flutter SDK and is a convenient way for widgets to observe changes in ViewModels. + + **3. Structure the Logic Layer (Optional):** + * The **Logic layer** implements core business logic and facilitates interaction between the data layer and UI layer. It is commonly known as the 'domain layer'. + * This layer is **optional** and only needs to be implemented if your application has complex business logic that happens on the client. Many apps are only concerned with presenting and changing data (CRUD apps). + + **4. Structure the Data Layer:** + * The **data layer** exposes application data to the rest of the app and contains most of the business logic. + * Use the **repository pattern** in the data layer. This pattern isolates data access logic from the rest of the application, creating an abstraction layer between business logic and underlying data storage mechanisms (databases, APIs, file systems, etc.). + * In practice, this means creating **Repository classes and Service classes**. + * Repositories and services represent the data of an application, or the model layer of MVVM. + * Services interact with external APIs, such as client servers and platform plugins. + + **5. Project Organization and Best Practices:** + * A single feature in an application might require a View, a ViewModel, one or more Repositories, and zero or more Services. + * Well-organized code is easier for multiple engineers to work on with minimal code conflicts and is easier for new engineers to navigate and understand. + * These recommendations are guidelines, not steadfast rules, and should be adapted to your unique requirements. + * The Flutter team strongly recommends implementing these practices when starting a new application and strongly considering refactoring existing apps unless it fundamentally clashes with the current approach. - * **UI layer** (also known as the 'presentation layer') displays data to - the user that is exposed by the business logic layer and handles user - interaction. In Flutter, this applies to widgets, which are the basic - building blocks of a Flutter user interface. You should - write reusable, lean widgets that hold as little logic as possible. - * **Logic layer** (commonly known as the 'domain layer') implements core - business logic and facilitates interaction between the data layer and UI - layer. This layer is optional and only needs to be implemented if your - application has complex business logic that happens on the client. Many - apps are only concerned with presenting data to a user and allowing the - user to change that data (colloquially known as CRUD apps). - * **Data layer** (known as the model in MVVM terminology) is the source of - truth for all application data. It handles business data and logic. As - the source of truth, it's the only place that application data should be - updated. It is responsible for consuming data from various external - APIs, exposing that data to the UI, handling events from the UI that - require data to be updated, and sending update requests to those - external APIs as needed. The data layer typically has two main - components: repositories and services. - * **Repositories** are the source of truth for your model data. They - contain logic related to that data, such as updating data in - response to user events or polling for data from services. - Repositories are responsible for synchronizing data when offline - capabilities are supported, managing retry logic, and caching data. - They transform raw data into domain models, which represent the data - the application needs, formatted for consumption by view model - classes. - * **Services** are stateless Dart classes that interact with APIs, - such as HTTP servers and platform plugins. Their only job is to wrap - an external API. Any data your application needs that isn't created - inside the application code itself should be fetched from within - service classes. There is generally one service class per data - source. - - Within each layer, you should further separate your application by feature - or functionality. For example, your application's authentication logic - should be in a different class than the search logic. - - The Flutter team's recommended app architecture includes common - architectural principles, MVVM and state management, dependency injection, - and common design patterns for writing robust Flutter applications. - resources: - https://docs.flutter.dev/app-architecture + - https://docs.flutter.dev/app-architecture/case-study + - https://docs.flutter.dev/app-architecture/case-study/data-layer + - https://docs.flutter.dev/app-architecture/case-study/ui-layer - https://docs.flutter.dev/app-architecture/concepts + - https://docs.flutter.dev/app-architecture/design-patterns - https://docs.flutter.dev/app-architecture/guide - - https://docs.flutter.dev/resources/architectural-overview - https://docs.flutter.dev/app-architecture/recommendations - - https://docs.flutter.dev/app-architecture/design-patterns - - https://docs.flutter.dev/app-architecture/case-study - - https://docs.flutter.dev/app-architecture/case-study/data-layer - - https://docs.flutter.dev/app-architecture/design-patterns/key-value-data - https://docs.flutter.dev/learn/pathway/how-flutter-works -- 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. + - https://docs.flutter.dev/reference/create-new-app + - https://docs.flutter.dev/resources/architectural-overview + - https://docs.flutter.dev/resources/faq + - https://docs.flutter.dev/ui/layout/tutorial +- name: flutter-build-responsive-layout + description: Use `LayoutBuilder`, `MediaQuery`, or `Expanded/Flexible` to + create a layout that adapts to different screen sizes. Use when you need the + UI to look good on both mobile and tablet/desktop form factors. instructions: | - In Flutter, almost everything is a widget, including layout models. Widgets - are classes used to build user interfaces (UIs) and UI elements. Visible - elements like images, icons, and text are widgets, as are invisible elements - like rows, columns, and grids that arrange, constrain, and align visible - widgets. Layouts are created by composing simpler widgets to build more - complex ones. - - Understanding constraints is crucial for Flutter layout, as layout generally - refers to the size and position of widgets on the screen. A widget's size - and position are constrained by its parent, meaning it cannot choose its own - size or placement. Instead, these are determined through a conversation - between the widget and its parent. - - When building a Flutter layout, it's helpful to plan the layout beforehand - to speed up coding. This can involve using visual cues, an interface design - tool, or even a pencil and paper to determine where elements should be - placed on the screen before writing code. Key questions to ask when breaking - down a layout include identifying rows, columns, grids, overlapping - elements, tabs, and what needs alignment, padding, or borders. Identifying - larger elements, such as an image, rows, and text blocks, is also important. - - Common layout patterns involve arranging widgets vertically or horizontally. - A `Row` widget arranges widgets horizontally, while a `Column` widget - arranges them vertically. Both `Row` and `Column` widgets take a list of - child widgets, and these children can themselves be other `Row`, `Column`, - or complex widgets. You can specify how `Row` or `Column` widgets align - their children, both vertically and horizontally, and how child widgets use - the available space. Nesting rows and columns inside each other is a common - practice. The `Container` widget is a versatile widget that allows - customization of its child, such as adding padding, margins, borders, or - background colors. - - Flutter's layout mechanism aims to enable the development of apps from a - single codebase that look and feel great on any platform, accommodating - various screen sizes and input devices. This involves concepts of adaptive - and responsive design. Responsive design focuses on fitting the UI into the - available space by adjusting the placement of design elements, while - adaptive design ensures the UI is usable within that space. + 1. **Determine the available space**: + * Use `MediaQuery.sizeOf(context)` or `LayoutBuilder` to get the size of the window your app is currently running in. + * `LayoutBuilder` builds a widget tree that can depend on the parent widget's size. Its builder callback provides `BoxConstraints` from its parent, which can be used to return different widgets based on the available space. + * Avoid using `MediaQuery.orientationOf` or `OrientationBuilder` near the top of your widget tree to switch between different app layouts, as the device's orientation doesn't necessarily inform you of the app window's space. + * Do not check for hardware types like "phone" or "tablet" when making layout decisions, as the space an app renders in isn't always tied to the full screen size of the device. Flutter apps can run in resizable windows, multi-window modes, or picture-in-picture. + * For large screen devices, `MediaQuery` can be used to get the app window size rather than the physical device size. + * Another approach is to use the `maxWidth` property of `BoxConstraints` by wrapping a widget like `GridView` in a `ConstrainedBox` or `Container`. + + 2. **Implement adaptive layouts based on available space**: + * Use `LayoutBuilder` to return different layouts based on the `BoxConstraints` received. For example, you can check `constraints.maxWidth` to determine if the screen is large (e.g., `maxWidth > largeScreenMinWidth`) and return a different widget tree accordingly. + * For small screens, you might use a navigation-style approach, while for large screens, you could use a `Row` to place elements side-by-side. + * Consider using adaptive breakpoints, such as those recommended by Material Design. + + 3. **Manage widget sizing and positioning within layouts**: + * Utilize `Expanded` and `Flexible` widgets within `Row`, `Column`, or `Flex` to distribute available space among children. + * `Expanded` stretches a child to fill any remaining available space. It is equivalent to `Flexible` with a `flex` factor of 1.0. + * `Flexible` allows a child to be sized to a specific size while still allowing it to expand or contract. You can specify a `flex` factor to determine the ratio in which `Expanded` or `Flexible` children consume available space. For example, a `flex` factor of 2 will make a widget twice as wide as others with a `flex` factor of 1. + * To prevent widgets from gobbling up all horizontal space on large screens, consider constraining their width. + * For lists with an unknown number of items, `ListView.builder` can be used to lazily render items. + + 4. **Consider specific device and orientation behaviors**: + * Avoid locking screen orientation, as it can cause issues on foldable devices, potentially leading to letterboxing where the app is centered with black borders. + * If screen orientation must be locked (though generally discouraged), use the `Display API` to get physical screen dimensions instead of `MediaQuery`. The `Display API` is useful in situations where `MediaQuery` might not receive the larger window size due to compatibility modes. + * Android and Flutter design guidance recommends not locking screen orientation. Android large format tiers require both portrait and landscape support. + * Support a variety of input devices, including basic mice, trackpads, and keyboard shortcuts. resources: - - https://docs.flutter.dev/ui/layout - - https://docs.flutter.dev/ui/layout/tutorial + - https://docs.flutter.dev/cookbook/design/orientation - https://docs.flutter.dev/get-started/fundamentals/layout - - https://docs.flutter.dev/ui/design/material - - https://docs.flutter.dev/learn/tutorial/layout + - https://docs.flutter.dev/learn/pathway/tutorial/adaptive-layout + - https://docs.flutter.dev/release/breaking-changes/layout-builder-optimization + - https://docs.flutter.dev/testing/common-errors + - https://docs.flutter.dev/ui - https://docs.flutter.dev/ui/adaptive-responsive - - https://docs.flutter.dev/learn/tutorial/adaptive-layout + - https://docs.flutter.dev/ui/adaptive-responsive/best-practices + - https://docs.flutter.dev/ui/adaptive-responsive/general + - https://docs.flutter.dev/ui/adaptive-responsive/large-screens + - https://docs.flutter.dev/ui/adaptive-responsive/safearea-mediaquery + - https://docs.flutter.dev/ui/layout - https://docs.flutter.dev/ui/layout/constraints + - https://docs.flutter.dev/ui/layout/tutorial - https://docs.flutter.dev/ui/widgets/layout - - https://docs.flutter.dev/learn/pathway/tutorial/layout -- name: flutter-theming-apps - description: Customizes the visual appearance of a Flutter app using the theming system. Use when defining global styles, colors, or typography for an application. +- name: flutter-setup-declarative-routing + description: Configure `MaterialApp.router` using a package like `go_router` + for advanced URL-based navigation. Use when developing web applications or + mobile apps that require specific deep linking and browser history support. instructions: | - Theming involves sharing colors and font styles throughout an app. You can - define app-wide themes and extend a theme to change a theme style for one - component. Each theme defines colors, type style, and other parameters - applicable for the type of Material component. Flutter applies styling in a - specific order: styles applied to the specific widget, themes that override - the immediate parent theme, and finally, the main theme for the entire app. - As of Flutter 3.16, Material 3 is Flutter's default theme. - - Component theme normalization is a process where themes like `CardTheme`, - `DialogTheme`, and `TabBarTheme` have been refactored to conform to - Flutter's conventions for component themes in the Material library. This - normalization aims for a more consistent theming experience. - `CardThemeData`, `DialogThemeData`, and `TabBarThemeData` were added to - define overrides for the defaults of the component visual properties. In - `ThemeData`, the type of the `cardTheme` property has changed from - `CardTheme` to `CardThemeData`. During card theme normalization, the type of - `ThemeData.cardTheme` was changed to `Object?` to accept both `CardTheme` - and `CardThemeData` for a smooth transition. - - New button widgets and themes have been introduced to align with the - Material Design spec and to make buttons more flexible and easier to - configure. The original `FlatButton`, `RaisedButton`, and `OutlineButton` - widgets have been replaced by `TextButton`, `ElevatedButton`, and - `OutlinedButton` respectively. Each new button class has its own theme: - `TextButtonTheme`, `ElevatedButtonTheme`, and `OutlinedButtonTheme`. The - original `ButtonTheme` class is no longer used. The appearance of these new - buttons is specified by a `ButtonStyle` object, which is a collection of - visual properties, many of which can depend on the button's state through - `MaterialStateProperty`. These new themes follow a "normalized" pattern - where theme properties and widget constructor parameters are null by - default, and non-null values override the component's default. The defaults - are based primarily on the overall `Theme`'s `colorScheme` and `textTheme`. - - When considering theming for adaptive apps, it's important to consider - platform standards, idioms, and norms, as these inform user expectations. - Reflecting these standards can reduce cognitive load and build user trust. - It's beneficial to envision the ideal user experience for each platform and - consider how a user of that platform would expect to achieve a goal. - Assigning a platform advocate for each platform can provide constant, - informed feedback to ensure the app feels great on each platform. While - conforming to expected behaviors, an app doesn't need to use default - components or styling; many popular multiplatform apps have distinct UIs. - Consolidating styling and behavior across platforms can simplify development - and testing. + 1. **Create a new Flutter application**: + * Use the command `flutter create `. For example, `flutter create deeplink_cookbook`. + + 2. **Add the `go_router` package as a dependency**: + * Run `flutter pub add go_router`. The Flutter team maintains the `go_router` package, which offers a simple API for complex routing scenarios. + + 3. **Configure `MaterialApp.router` with a `GoRouter` object**: + * In your `main.dart` file, import `package:go_router/go_router.dart'` and `package:flutter/material.dart'`. + * Create a `GoRouter` object that defines your application's routes. This object will handle routing, including paths like '/' and '/details'. + * Set up your `main` function to run `MaterialApp.router` and provide it with the `routerConfig` property, which should be your `GoRouter` object. + * For example, you can define routes within the `GoRouter` object using `GoRoute` for different paths and their corresponding builders. + * Routing packages like `go_router` are declarative, meaning they will consistently display the same screen(s) when a deep link is received. + + 4. **Configure deep linking for specific platforms (if needed)**: + * **For iOS:** + * If using a Flutter version earlier than 3.27, manually opt-in to deep linking by adding `FlutterDeepLinkingEnabled` with a value of `YES` to `Info.plist`. + * If using third-party plugins for deep links (e.g., `app_links`), you should opt out of Flutter's default deep link handler by setting `FlutterDeepLinkingEnabled` to `NO` in `Info.plist`. + * Add associated domains by launching Xcode, selecting the top-level Runner, clicking the Runner target, and then "Signing & Capabilities". Add a new capability for "Associated Domains" and enter `applinks:`. + * Open `ios/Runner/Runner.entitlements` and add the associated domain inside the `` tag. + * Host an `apple-app-site-association` file in your web domain, which tells the mobile browser to open your iOS app instead of the browser. This file requires your app's `appID`, formatted as `.`. + * **For Android:** + * Web apps read the deep link path from the URL fragment by default, using the pattern `/#/path/to/app/screen`, but this can be changed by configuring the URL strategy. + * To configure your Android application to handle deep links, refer to the deep linking documentation. resources: - - https://docs.flutter.dev/cookbook/design/themes - - https://docs.flutter.dev/release/breaking-changes/component-theme-normalization - - https://docs.flutter.dev/release/breaking-changes/buttons - - https://docs.flutter.dev/release/breaking-changes/component-theme-normalization-updates - - https://docs.flutter.dev/release/breaking-changes/theme-data-accent-properties - - https://docs.flutter.dev/release/breaking-changes/material-theme-system-updates - - https://docs.flutter.dev/ui/design - - https://docs.flutter.dev/release/breaking-changes/appbar-theme-color - - https://docs.flutter.dev/ui/widgets/styling - - https://docs.flutter.dev/release/breaking-changes/material-3-migration - - https://docs.flutter.dev/cookbook/design/themes - - https://docs.flutter.dev/release/breaking-changes/component-theme-normalization - - https://docs.flutter.dev/release/breaking-changes/buttons - - https://docs.flutter.dev/release/breaking-changes/theme-data-accent-properties - - https://docs.flutter.dev/ui/design - - https://docs.flutter.dev/release/breaking-changes/component-theme-normalization-updates - - https://docs.flutter.dev/release/breaking-changes/material-theme-system-updates - - https://docs.flutter.dev/release/breaking-changes/material-3-migration - - https://docs.flutter.dev/release/breaking-changes/material-3-default - - https://docs.flutter.dev/release/breaking-changes/appbar-theme-color - - https://docs.flutter.dev/cookbook/design/themes - - https://docs.flutter.dev/ui/adaptive-responsive/idioms - - https://docs.flutter.dev/ui -- 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. - instructions: | - To build a form with validation, you need to create a `Form` widget with a - `GlobalKey`. This `GlobalKey` uniquely identifies the `Form` widget and - allows for validation later. It's recommended to create the form as a - `StatefulWidget` to store the `GlobalKey()` once, as generating a - new `GlobalKey` each time the `build` method runs is resource-expensive. The - `Form` widget acts as a container for grouping and validating multiple form - fields. While a `GlobalKey` is the recommended way to access a form, - `Form.of()` can be used in more complex widget trees. - - Next, add a `TextFormField` with validation logic. The `TextFormField` - widget renders a material design text field and can display validation - errors. Input validation is done by providing a `validator()` function to - the `TextFormField`. If the user's input is invalid, the `validator` - function should return a `String` containing an error message. If there are - no errors, the `validator` must return `null`. For example, a validator can - ensure the `TextFormField` isn't empty by returning a message like 'Please - enter some text' if the value is null or empty, otherwise returning null. - `TextFormField` is a convenience widget that pre-wraps a `TextField` in a - `FormField`. Each individual form field should be wrapped in a `FormField` - widget with the `Form` widget as a common ancestor. - - Finally, create a button to validate and submit the form. When the user - attempts to submit the form, you check if the form is valid. To validate the - form, use the `_formKey` created earlier. The `_formKey.currentState` - accessor provides access to the `FormState`, which is automatically created - by Flutter when building a `Form`. The `FormState` class contains the - `validate()` method. When `validate()` is called, it runs the `validator()` - function for each text field in the form. If all validations pass, - `validate()` returns `true`. If any text field contains errors, `validate()` - rebuilds the form to display error messages and returns `false`. If the form - is valid, you can display a success message (e.g., a `SnackBar`). If it's - not valid, the error messages from the `TextFormField` validators will be - displayed. The `FormState` also allows you to save, reset, and validate each - `FormField` that descends from the `Form`. - resources: - - https://docs.flutter.dev/ai-best-practices/developer-experience - - https://docs.flutter.dev/app-architecture/recommendations - - https://docs.flutter.dev/cookbook/forms - - https://docs.flutter.dev/cookbook/forms/text-input - - https://docs.flutter.dev/cookbook/forms/validation - - https://docs.flutter.dev/flutter-for/react-native-devs - - https://docs.flutter.dev/flutter-for/xamarin-forms-devs - - https://docs.flutter.dev/get-started/fundamentals/user-input - - https://docs.flutter.dev/release/breaking-changes/form-field-autovalidation-api - - https://docs.flutter.dev/ui/adaptive-responsive/best-practices - - https://docs.flutter.dev/ui/interactivity/input - -- 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. - instructions: | - Routing and navigation involve moving between different screens or "routes" - within an application. In Flutter, screens and pages are referred to as - routes. This concept is similar to an Activity in Android or a - ViewController in iOS, but in Flutter, a route is simply a widget. - - A key aspect of navigation is deep linking, which allows an app to open - directly to a specific location or "deep" inside the app when a URL is - received. For example, a deep link from an advertisement could open a - shopping app to a specific product page. Flutter supports deep linking on - iOS, Android, and the web. When running an app in a web browser, no - additional setup is required for deep linking. While named routes can be - used for deep linking, they are generally not recommended for most - applications. The `Router` widget can also be used for deep linking. - - Common navigation tasks include navigating to a new screen and returning to - the previous one. This can be achieved using `Navigator.push()` to go to a - new route and `Navigator.pop()` to return to the previous route. Other - navigation functionalities include adding tabs to an app, sending data to a - new screen, returning data from a screen, and adding a drawer to a screen. - It's also possible to create nested navigation flows. - - For more in-depth information on navigation and routing, resources like the - Flutter cookbook offer multiple navigation recipes that demonstrate how to - use the `Navigator`. The `Navigator` and `Router` API documentation provide - details on setting up declarative navigation without needing a routing - package. Additionally, the Material Design documentation offers insights - into designing app navigation, covering concepts like forward, upward, and - chronological navigation. An article on Medium describes how to use the - `Router` widget directly. The `Router` design document explains the - motivation and design of the `Router` API. A sample demonstrating how to use - `go_router` API for common navigation scenarios is also available. - resources: - - https://docs.flutter.dev/ui/navigation - - https://docs.flutter.dev/cookbook/navigation/navigation-basics - - https://docs.flutter.dev/cookbook/navigation/named-routes + - https://docs.flutter.dev/cookbook/animation/page-route-animation - https://docs.flutter.dev/cookbook/effects/nested-nav + - https://docs.flutter.dev/cookbook/navigation/named-routes - https://docs.flutter.dev/cookbook/navigation/navigate-with-arguments - - https://docs.flutter.dev/ui/navigation/deep-linking - - https://docs.flutter.dev/learn/tutorial/navigation - - https://docs.flutter.dev/learn/pathway/tutorial/navigation - - https://docs.flutter.dev/cookbook/navigation - - https://docs.flutter.dev/cookbook/navigation/passing-data - - https://docs.flutter.dev/cookbook/navigation/navigate-with-arguments + - https://docs.flutter.dev/cookbook/navigation/navigation-basics + - https://docs.flutter.dev/cookbook/navigation/set-up-app-links + - https://docs.flutter.dev/cookbook/navigation/set-up-universal-links + - https://docs.flutter.dev/packages-and-plugins/using-packages + - https://docs.flutter.dev/platform-integration/web + - https://docs.flutter.dev/platform-integration/web/building + - https://docs.flutter.dev/platform-integration/web/embedding-flutter-web + - https://docs.flutter.dev/platform-integration/web/initialization + - https://docs.flutter.dev/release/breaking-changes/deep-links-flag-change + - https://docs.flutter.dev/release/breaking-changes/route-navigator-refactoring + - https://docs.flutter.dev/tools/devtools/deep-links - https://docs.flutter.dev/ui/navigation - - https://docs.flutter.dev/learn/pathway/tutorial/navigation - - https://docs.flutter.dev/cookbook/effects/nested-nav - - https://docs.flutter.dev/cookbook/navigation/navigate-with-arguments - - https://docs.flutter.dev/cookbook/navigation/named-routes - - https://docs.flutter.dev/cookbook/navigation/passing-data - - https://docs.flutter.dev/resources/glossary/index.html.md -- 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. + - https://docs.flutter.dev/ui/navigation/deep-linking + - https://docs.flutter.dev/ui/navigation/url-strategies + - https://pub.dev/documentation/go_router/latest/topics/Configuration-topic.html + - https://pub.dev/documentation/go_router/latest/topics/Deep%20linking-topic.html + - https://pub.dev/documentation/go_router/latest/topics/Error%20handling-topic.html + - https://pub.dev/documentation/go_router/latest/topics/Get%20started-topic.html + - https://pub.dev/documentation/go_router/latest/topics/Named%20routes-topic.html + - https://pub.dev/documentation/go_router/latest/topics/Navigation-topic.html + - https://pub.dev/documentation/go_router/latest/topics/Redirection-topic.html + - https://pub.dev/documentation/go_router/latest/topics/State%20restoration-topic.html + - https://pub.dev/documentation/go_router/latest/topics/Transition%20animations-topic.html + - https://pub.dev/documentation/go_router/latest/topics/Type-safe%20routes-topic.html + - https://pub.dev/documentation/go_router/latest/topics/Upgrading-topic.html + - https://pub.dev/documentation/go_router/latest/topics/Web-topic.html +- name: flutter-add-widget-test + description: Implement a component-level test using `WidgetTester` to verify + UI rendering and user interactions (tapping, scrolling, entering text). Use + when validating that a specific widget displays correct data and responds to + events as expected. instructions: | - Animation is a broad topic that encompasses various types and patterns, - often implemented using a system based on typed `Animation` objects. - - Generally, animations are categorized as either tween-based or - physics-based. In a tween animation, the beginning and ending points, a - timeline, and a curve defining timing and speed are all defined, and the - framework calculates the transition. Physics-based animation, on the other - hand, models motion to resemble real-world behavior, such as how a tossed or - dropped ball would move. - - Common animation patterns include animated lists or grids, shared element - transitions, and staggered animations. An animated list or grid involves - animating the addition or removal of elements. Shared element transitions, - also known as hero animations, occur when a user selects an element, and the - UI animates that element to a new page with more detail. Hero animations can - involve the element flying between routes while changing position and size, - or its shape transforming, for example, from circular to rectangular. - Staggered animations are broken into smaller motions, with some of the - motion delayed. - - The animation system in Flutter is built upon typed `Animation` objects. - Widgets can either directly incorporate these animations by reading their - current value and listening to state changes, or they can use them as a - basis for more elaborate animations passed to other widgets. An `Animation` - object is an abstract class that understands its current value and state - (completed or dismissed). It knows nothing about what is onscreen, - rendering, or `build()` functions. A common type is `Animation`, - which sequentially generates interpolated numbers between two values over a - duration, with output that can be linear, curved, or a step function. - Animations can also interpolate other types like `Animation` or - `Animation`. The current value of an `Animation` object is always - available in its `.value` member. When an animation's value changes, it - notifies all listeners added with `addListener`. - - `Tween` is an abstract class that maps a double value, typically in the - range 0.0-1.0, to a typed value such as a `Color` or another double. Tweens - are stateless and immutable, having a notion of an output type, a beginning - and end value, and a way to interpolate between them. The `Curve` abstract - class maps doubles, also nominally in the range 0.0-1.0, to other doubles in - the same range, and `Curve` classes are also stateless and immutable. - - Flutter offers implicit animations as a simple way to implement animations. - Implicit animations are a group of widgets that automatically animate - changes to their properties without requiring manual management of - intermediate behavior. An example of a versatile implicit animation widget - is `AnimatedContainer`. + 1. **Add the `flutter_test` dependency**. This dependency should be included in the `dev_dependencies` section of your `pubspec.yaml` file. If you created your Flutter project using command-line tools or a code editor, this dependency might already be present. + 2. **Create the widget to test**. For example, you might create a widget that displays a title and a message. + 3. **Create a `testWidgets` test**. Use the `testWidgets()` function from the `flutter_test` package to define your test. This function automatically provides a `WidgetTester` for each test case. The `WidgetTester` allows you to build and interact with widgets in the test environment. + 4. **Build the widget using the `WidgetTester`**. Use the `pumpWidget()` method provided by `WidgetTester` to build and render your widget within the test environment. For instance, you can create an instance of `MyWidget` with specific title and message values. After the initial `pumpWidget()` call, `WidgetTester` offers additional methods like `tester.pump()` to rebuild the widget, which is useful for `StatefulWidget`s or animations where Flutter doesn't automatically rebuild after state changes in the test environment. + 5. **Search for the widget using a `Finder`**. `Finder` classes allow you to locate widgets in the test environment. For example, you can use `find.text('Your Text')` to find a `Text` widget. + 6. **Verify the widget using a `Matcher`**. Widget-specific `Matcher` constants help confirm whether a `Finder` successfully locates a widget or multiple widgets. For example, `findsOneWidget` can verify that a `Text` widget appears exactly once in the widget tree. You can also use `matchesGoldenFile` to verify that a widget's rendering matches a specific bitmap image (golden file testing). + 7. **Simulate user interactions**. The `WidgetTester` provides methods for simulating interactions like entering text, tapping, and dragging. + * To enter text, use `enterText()`. + * To tap, use `tap()`. + * To drag, use `drag()`. + * To handle scrolling, the `WidgetTester` class provides the `scrollUntilVisible()` method, which scrolls until a specific widget is visible. This is useful when the height of items in a list can vary. + 8. **Rebuild the widget tree after interactions**. Since Flutter doesn't automatically rebuild widgets in the test environment after user interactions update the app's state, you need to call `pump()` or `pumpAndSettle()` methods provided by the `WidgetTester` to ensure the widget tree is rebuilt. + 9. **Run the tests**. Execute the tests using the command `flutter test test/widget_test.dart` from the root of your project. + + A widget test (also known as a component test in other UI frameworks) aims to verify that a single widget's UI looks and interacts as expected. It involves multiple classes and requires a test environment that provides the appropriate widget lifecycle context. Widget tests are more comprehensive than unit tests because the widget being tested can receive user actions, perform layout, and instantiate child widgets. However, like unit tests, the widget test environment is a simpler implementation than a full UI system. resources: - - https://docs.flutter.dev/ui/animations - - https://docs.flutter.dev/ui/animations/overview - - https://docs.flutter.dev/ui/animations/tutorial - - https://docs.flutter.dev/learn/pathway/tutorial/implicit-animations - - https://docs.flutter.dev/learn/tutorial/implicit-animations - - https://docs.flutter.dev/ui/widgets/animation - - https://docs.flutter.dev/ui/animations/staggered-animations - - https://docs.flutter.dev/cookbook/animation/page-route-animation - - https://docs.flutter.dev/cookbook/animation/animated-container - - https://docs.flutter.dev/ui/animations/hero-animations - - https://docs.flutter.dev/ui/animations - - https://docs.flutter.dev/ui/animations/overview - - https://docs.flutter.dev/ui/animations/tutorial - - https://docs.flutter.dev/learn/pathway/tutorial/implicit-animations - - https://docs.flutter.dev/learn/tutorial/implicit-animations - - https://docs.flutter.dev/ui/animations/staggered-animations - - https://docs.flutter.dev/cookbook/animation/animated-container - - https://docs.flutter.dev/cookbook/animation/page-route-animation - - https://docs.flutter.dev/ui/animations/hero-animations - - https://docs.flutter.dev/cookbook/animation/physics-simulation - - https://docs.flutter.dev/ui/animations - - https://docs.flutter.dev/ui/animations/overview - - https://docs.flutter.dev/ui/animations/tutorial - - https://docs.flutter.dev/learn/pathway/tutorial/implicit-animations - - https://docs.flutter.dev/learn/tutorial/implicit-animations - - https://docs.flutter.dev/ui/animations/staggered-animations - - https://docs.flutter.dev/cookbook/animation/animated-container - - https://docs.flutter.dev/cookbook/animation/page-route-animation - - https://docs.flutter.dev/cookbook/animation/animated-container - - https://docs.flutter.dev/ui/widgets/animation - - https://docs.flutter.dev/cookbook/animation/page-route-animation -- name: flutter-localizing-apps - description: Configures a Flutter app to support multiple languages and regions. Use when preparing an application for international markets and diverse user locales. + - https://docs.flutter.dev/app-architecture/case-study/testing + - https://docs.flutter.dev/cookbook/testing/integration/introduction + - https://docs.flutter.dev/cookbook/testing/unit/introduction + - https://docs.flutter.dev/cookbook/testing/widget/introduction + - https://docs.flutter.dev/cookbook/testing/widget/orientation + - https://docs.flutter.dev/cookbook/testing/widget/scrolling + - https://docs.flutter.dev/cookbook/testing/widget/tap-drag + - https://docs.flutter.dev/get-started/fundamentals/user-input + - https://docs.flutter.dev/release/breaking-changes/mock-platform-channels + - https://docs.flutter.dev/testing + - https://docs.flutter.dev/testing/integration-tests + - https://docs.flutter.dev/testing/overview + - https://docs.flutter.dev/testing/testing-plugins +- name: flutter-setup-localization + description: Add `flutter_localizations` and `intl` dependencies, enable + "generate true" in `pubspec.yaml`, and create an `l10n.yaml` configuration + file. Use when initializing localization support for a new Flutter project. instructions: | - Localization in Flutter involves adapting an app to different languages and - regions. By default, Flutter only supports US English for its strings. To add - support for other languages, you need to include the `flutter_localizations` - package. You might also need Dart's `intl` package for internationalization - (i10n) features like date/time formatting. - - When a `WidgetsApp` or `MaterialApp` is initialized, it creates a - `Localizations` widget with specified delegates. The device's current locale - is always accessible from the `Localizations` widget via a `Locale` object or - `Window.locale`. To access localized resources, you use the - `Localizations.of()` method to get a specific localizations class provided by - a delegate. The `intl_translation` package can be used to extract - translatable copy into `.arb` files for translation and then import them back - into the app for use with `intl`. - - To set up an internationalized app, you add `flutter_localizations` and - `intl` as dependencies in your `pubspec.yaml` file. Then, you import the - `flutter_localizations` library and specify `localizationsDelegates` and - `supportedLocales` for your `MaterialApp` or `CupertinoApp`. For example, you - would include `GlobalMaterialLocalizations.delegate`, - `GlobalWidgetsLocalizations.delegate`, and - `GlobalCupertinoLocalizations.delegate` in `localizationsDelegates`. You also - define the `supportedLocales`, such as `Locale('en')` for English and - `Locale('es')` for Spanish. After this setup, Material and Cupertino packages - should be correctly localized, and widgets should adapt to localized messages - and layout (e.g., left-to-right or right-to-left). - - The `Localizations` widget is crucial for loading and looking up objects - containing collections of localized values. Apps refer to these objects using - `Localizations.of(context, type)`. If the device's locale changes, the - `Localizations` widget automatically loads values for the new locale and - rebuilds dependent widgets. This behavior is similar to an `InheritedWidget`. - The `WidgetsApp` widget creates a `Localizations` widget and rebuilds it if - the system's locale changes. You can always find an app's current locale - using `Localizations.localeOf(context)`. - - Some widgets, like `TextField`, require a `MaterialLocalizations` widget in - the widget tree. If `MaterialLocalizations` is not present, `TextField` will - throw an assertion error. You can introduce `MaterialLocalizations` by using - a `MaterialApp` at the root of your application, which includes them - automatically, or by adding a `Localization` widget with a - `MaterialLocalizations` delegate. - - For iOS, even though Flutter handles localizations, you need to add the - supported languages in the Xcode project. This ensures that your app's entry - in the App Store correctly displays the supported languages. You can - configure supported locales by opening your project's `ios/Runner.xcodeproj` - file in Xcode, selecting the `Runner` project file, and then the `Info` tab. - In the `Localizations` section, you click the Add button to add supported - languages and regions. Xcode will then create empty `.strings` files and - update the `ios/Runner.xcodeproj/project.pbxproj` file, which the App Store - uses to determine supported languages. - - Advanced locale definition might be necessary for languages with multiple - variants, such as Chinese, which requires specifying language code, script - code, and country code to differentiate between simplified and traditional - script, and regional differences. Similarly, other languages like French - (fr_FR, fr_CA) should be fully differentiated for nuanced localization. The - `Locale` class identifies the user's language, and internationalized apps - respond by displaying locale-specific values. + 1. **Add `flutter_localizations` and `intl` dependencies**: + * Add `flutter_localizations` and `intl` packages to your `pubspec.yaml` file. You can do this by running `$ flutter pub add flutter_localizations --sdk=flutter` and `$ flutter pub add intl:any`. + * The `flutter_localizations` package is a Flutter SDK package that enables the localization of ARB files and is often used with the `intl` package. + * After adding the packages, your `pubspec.yaml` file should include entries like: + ```yaml + dependencies: + flutter: + sdk: flutter + flutter_localizations: + sdk: flutter + intl: any + ``` + + * After adding a package dependency, run `flutter pub get` in your terminal to install it. + + 2. **Enable `generate: true` in `pubspec.yaml`**: + * Open your `pubspec.yaml` file. + * In the `flutter` section, add `generate: true`. This flag handles localization tasks. + * The `flutter` section in `pubspec.yaml` is specific to Flutter. + + 3. **Create an `l10n.yaml` configuration file**: + * Add a new YAML file named `l10n.yaml` to the root directory of your Flutter project. + * This file can be used to specify options like `synthetic-package: false` to generate localized messages directly into source files instead of a synthetic package. You can also specify `arb-dir` or `output-dir` in this file. + + 4. **Configure `MaterialApp` or `CupertinoApp`**: + * Import the `flutter_localizations` library. + * Specify `localizationsDelegates` and `supportedLocales` for your `MaterialApp` or `CupertinoApp`. For example: + ```dart + import 'package:flutter_localizations/flutter_localizations.dart'; + // ... + return const MaterialApp( + title: 'Localizations Sample App', + localizationsDelegates: [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: [ + Locale('en'), // English + Locale('es'), // Spanish + ], + home: MyHomePage(), + ); + ``` + + * After these steps, Material and Cupertino packages should be correctly localized in the supported locales, and widgets should adapt to localized messages and layout. resources: - - https://docs.flutter.dev/release/breaking-changes/cupertino-tab-bar-localizations - https://docs.flutter.dev/ui/internationalization - - https://docs.flutter.dev/release/breaking-changes/flutter-generate-i10n-source - - https://docs.flutter.dev/release/breaking-changes/material-localized-strings - - https://docs.flutter.dev/release/breaking-changes/text-field-material-localizations -- 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. +- name: flutter-use-http-package + description: Use the `http` package to execute GET, POST, PUT, or DELETE + requests. Use when you need to fetch from or send data to a REST API. instructions: | - Accessibility is crucial for building high-quality applications that can be - used by a broad range of users. Poorly designed applications create barriers - for people of all ages. There is a moral and legal imperative to ensure - universal access to information systems, as stated by the UN Convention on - the Rights of Persons with Disabilities. Many countries enforce accessibility - as a requirement, and companies recognize the business advantages of - maximizing access to their services. It is strongly encouraged to include an - accessibility checklist as a key criterion before shipping an app. - - Accessibility standards and regulations help ensure products are accessible - to people with disabilities. Many of these have been enacted into laws and - policies, making them requirements for products and services. Flutter - supports developers in making their apps more accessible by providing - first-class framework support for accessibility, in addition to what the - underlying operating system offers. This support includes UI Design and - styling, and Assistive Technologies like Screen Readers. - - To create an accessible app, UI design should consider accessibility. Key - aspects of accessible UI design and styling include large fonts, sufficient - contrast, and appropriate tap target sizes. Flutter text widgets respect the - operating system's font size settings, calculating font sizes automatically - based on the OS setting. Developers should ensure their layout has enough - room to render all its contents when font sizes are increased. Sufficient - color contrast makes text and images easier to read, benefiting users with - visual impairments and all users in extreme lighting conditions. The W3C - recommends a contrast ratio of at least 4.5:1 for small text and 3.0:1 for - large text. - - Flutter provides a catalog of accessibility widgets to help make apps - accessible. These include `ExcludeSemantics`, which drops the semantics of - its descendants, `MergeSemantics`, which merges the semantics of its - descendants, and `Semantics`, which annotates the widget tree with a - description of the meaning of widgets for accessibility tools. - - For web accessibility, Flutter renders its UI on a single canvas, requiring a - special layer to expose the UI's meaning and structure to web browsers. - Flutter's web accessibility is not on by default for performance reasons. - Users can enable it by pressing an invisible button with `aria-label="Enable - accessibility"`, which then reflects accessibility information in the DOM - tree. Alternatively, accessibility mode can be turned on in code by adding - `SemanticsBinding.instance.ensureSemantics();` when running the app. Semantic - roles define the purpose of a UI element, helping screen readers and other - assistive tools interpret and present the application effectively. These - roles can indicate if a widget is a button, link, heading, slider, or part of - a table. While standard Flutter widgets often provide semantics - automatically, custom components may need explicit role assignment to be - comprehensible to screen reader users. Flutter provides the `Semantics` - widget with the `SemanticsRole` enum to assign specific roles to widgets. - These Flutter-specific roles are translated into corresponding ARIA roles in - the web page's HTML structure. Using standard widgets like `TabBar`, - `MenuAnchor`, and `Table` is preferred as they handle many accessibility - aspects out-of-the-box. For custom components or when default semantics are - insufficient, the `Semantics` widget can be used to define or override roles. - - Adaptive design is about the UI being usable in the space, while responsive - design is about fitting the UI into the space. An adaptive app selects the - appropriate layout (e.g., bottom nav instead of side panel) and input devices - (e.g., mouse vs. touch) to feel natural on the current device. Responsive - design adjusts the placement of design elements to fit the available space. - Flutter aims to create a framework that allows apps from a single codebase to - look and feel great on any platform, accommodating various screen sizes and - input devices. It's important to design to the strengths of each form factor, - considering whether to focus on specific capabilities or even remove features - on certain device categories. - + 1. **Add the `http` package as a dependency**. + * Run `flutter pub add http` in your terminal. + 2. **Import the `http` package**. + * Add `import 'package:http/http.dart' as http;` to your Dart file. + 3. **Configure platform-specific permissions (if deploying to Android or macOS)**. + * For Android, add the Internet permission to your `AndroidManifest.xml` file: ``. + * For macOS, include the network client entitlement in `macos/Runner/DebugProfile.entitlements` and `macos/Runner/Release.entitlements` files: `com.apple.security.network.client`. + 4. **Make a network request**. + * The `http` package provides methods for GET, POST, PUT, and DELETE operations. + * For example, to make a GET request, use `http.get(Uri.parse('your_url'))`. + * To make a POST request, use `http.post()`. + * To make a PUT request, use `http.put()`. + * You can add authorization headers to your requests using the `headers` parameter. For example: `headers: {HttpHeaders.authorizationHeader: 'Basic your_api_token_here'}`. + * For POST requests, you might need to set `Content-Type` headers and encode the body as JSON. + 5. **Handle the response**. + * The `http.Response` object contains the server's response. + * Check the `statusCode` of the response to determine success or failure (e.g., `200 OK` for success, `201 CREATED` for a successful POST). + * Parse the JSON response if applicable. + * Convert the response into a custom Dart object. + * Handle errors by throwing exceptions, especially for non-success status codes, to prevent indefinite loading indicators. + 6. **Display the data with Flutter**. + * Use widgets like `FutureBuilder` to display data fetched asynchronously and handle loading and error states. + * For long-running tasks, consider showing a `ProgressIndicator` widget. + * For expensive computations like JSON parsing, perform them in a separate Isolate to avoid UI jank. resources: - - https://docs.flutter.dev/ui/accessibility/assistive-technologies - - https://docs.flutter.dev/ui/accessibility/accessibility-testing - - https://docs.flutter.dev/release/breaking-changes/tooltip-semantics-order - - https://docs.flutter.dev/ui/widgets/accessibility - - https://docs.flutter.dev/accessibility-and-localization/accessibility - - https://docs.flutter.dev/ui/accessibility - - https://docs.flutter.dev/ui/adaptive-responsive - - https://docs.flutter.dev/ui/adaptive-responsive/best-practices - - https://docs.flutter.dev/ui/accessibility/ui-design-and-styling - - https://docs.flutter.dev/ui/adaptive-responsive/input - - https://docs.flutter.dev/ui/accessibility/web-accessibility - - https://docs.flutter.dev/resources/glossary -- name: flutter-managing-state - description: Manages application and ephemeral state in a Flutter app. Use when sharing data between widgets or handling complex UI state transitions. - instructions: | - State management in Flutter refers to how an app organizes its objects to - effectively access them and share them between different widgets. State is - information that can be read synchronously when a widget is built or - information that might change during the lifetime of a widget. Flutter's UI - is built to reflect the current state of the app, meaning that when state - changes, the app should trigger a rebuild of the UI that depends on that - state. This concept is often described as "UI is a function of state". - - There are various approaches to state management in Flutter. A fundamental - concept is the distinction between `StatelessWidget` and `StatefulWidget`. A - `StatelessWidget` does not require a state change and has no internal state - to manage. Examples include `AboutDialog`, `CircleAvatar`, and `Text`. Its - `build` method is typically called when the widget is inserted into a tree, - when its parent changes its configuration, or when an `InheritedWidget` it - depends on changes. In contrast, a `StatefulWidget` is a widget that changes - state. If a widget's unique characteristics need to change based on user - interaction or other factors, it is stateful. `StatefulWidget`s store - mutable state in a separate class that subclasses `State`. To signal to the - framework that its internal state has changed, `setState()` must be called. - Calling `setState()` marks the widget as dirty and schedules it to be - rebuilt. If `setState()` is not called when modifying the internal state, - the UI might not update. - - For sharing state between widgets, common patterns include using widget - constructors (also known as "prop drilling"), `InheritedWidget` (or similar - APIs like the `provider` package), and callbacks to notify a parent widget - of changes. `InheritedWidget` is a low-level approach used for communication - between ancestors and children in the widget tree. It includes an - `updateShouldNotify()` method that Flutter calls to determine if a state - change should trigger a rebuild of child widgets. Flutter itself extensively - uses `InheritedWidget` for shared state, such as the application's visual - theme. The `provider` package is a utility package that provides a wrapper - around `InheritedWidget`. - - As applications grow, more advanced state management approaches become - attractive. Community-provided packages can help reduce boilerplate code, - provide debugging tools, and enable a clearer application architecture. The - best choice of package depends on the app's complexity, team preferences, - and specific problems. - - The Model-View-ViewModel (MVVM) design pattern is often used with reactive - frameworks like Flutter. In MVVM, the `ViewModel` manages the UI state and - the `View` displays that state. `ViewModels` contain the logic to convert - app data into UI State, as data from repositories might be formatted - differently from what needs to be displayed. `ViewModels` use Flutter's - `ChangeNotifier` class to notify the UI when data changes. Extending - `ChangeNotifier` provides access to the `notifyListeners()` method, which - triggers UI rebuilds when called. The `Model` in MVVM is typically a Dart - class that handles low-level tasks like HTTP requests, data caching, or - managing system resources. - - Flutter's architectural overview also highlights the concept of - unidirectional data flow (UDF). In UDF, state flows from the data layer - through the logic layer to the UI layer, while events from user interaction - flow in the opposite direction. The UI layer handles user events, which - invoke methods in the logic layer. The logic layer then calls methods in the - data layer (repositories) to mutate data. The data layer updates the data - and provides it back to the logic layer, which then sends its new state to - the UI for display. The key idea is that data changes always happen in the - Single Source of Truth (SSOT), which is the data layer. This makes code - easier to understand and less error-prone. - - resources: - - https://docs.flutter.dev/app-architecture/concepts - - https://docs.flutter.dev/app-architecture/design-patterns - - https://docs.flutter.dev/app-architecture/design-patterns/optimistic-state - - https://docs.flutter.dev/app-architecture/guide - - https://docs.flutter.dev/app-architecture/recommendations - - https://docs.flutter.dev/data-and-backend/state-mgmt - - https://docs.flutter.dev/data-and-backend/state-mgmt/declarative - - https://docs.flutter.dev/data-and-backend/state-mgmt/ephemeral-vs-app - - https://docs.flutter.dev/data-and-backend/state-mgmt/intro - - https://docs.flutter.dev/data-and-backend/state-mgmt/options - - https://docs.flutter.dev/data-and-backend/state-mgmt/simple - - https://docs.flutter.dev/get-started/flutter-for/react-native-devs - - https://docs.flutter.dev/get-started/fundamentals/local-caching - - https://docs.flutter.dev/get-started/fundamentals/state-management - - https://docs.flutter.dev/learn/pathway - - https://docs.flutter.dev/learn/pathway/tutorial/change-notifier - - https://docs.flutter.dev/learn/tutorial/change-notifier - - https://docs.flutter.dev/perf/isolates - - https://docs.flutter.dev/resources/architectural-overview - - https://docs.flutter.dev/ui -- 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. - instructions: | - HTTP and JSON are fundamental for networking and data handling in - applications. - - **HTTP (Hypertext Transfer Protocol)** - HTTP is used for networking, allowing applications to fetch, send, update, - and delete data over the internet. The `http` package provides a simple way - to issue HTTP requests. This package is supported across various platforms - including Android, iOS, macOS, Windows, Linux, and the web. When making HTTP - requests, it's crucial to handle network errors; a status code of 200 - typically indicates success, while other codes signify errors. For Android - apps, declaring internet usage in the `AndroidManifest.xml` is required. - Similarly, macOS apps need to allow network access in their `.entitlements` - files. - - Common HTTP operations include fetching data from an API, making - authenticated requests, sending data, updating data, and deleting data. When - building HTTP requests, it's recommended to use constructors like - `Uri.https` to safely build URLs, as this handles encoding and formatting - more reliably than string concatenation. Asynchronous operations in HTTP - requests are managed using `async` and `await` keywords, where `async` marks - a method as asynchronous and `await` pauses execution until a `Future` - completes. - - **JSON (JavaScript Object Notation)** - JSON is a lightweight data-interchange format often used with HTTP requests - for sending and receiving structured data. Applications frequently parse - JSON in the background. When a server returns a 200 OK response, the JSON - data can be parsed. For example, a `Summary` instance can be created from a - JSON map using a `fromJson` constructor. Similarly, an `ImageFile` can be - deserialized from JSON. - - Reliable JSON output is essential for integrating AI into applications. When - working with AI models, you can specify the output schema in the system - instruction to ensure reliable JSON output. This allows for parsing the - model's text response as JSON and extracting specific data fields like - width, height, grid data, and clues data. JSON serialization is a key aspect - of data handling. For instance, when updating data over the internet, the - request body can be encoded into JSON using `jsonEncode`. Conversely, - `jsonDecode` is used to parse the JSON response body. - resources: - - https://docs.flutter.dev/ai/best-practices/structure-output + - https://docs.flutter.dev/cookbook/networking/authenticated-requests - https://docs.flutter.dev/cookbook/networking/background-parsing - https://docs.flutter.dev/cookbook/networking/delete-data - https://docs.flutter.dev/cookbook/networking/fetch-data - https://docs.flutter.dev/cookbook/networking/send-data - https://docs.flutter.dev/cookbook/networking/update-data - - https://docs.flutter.dev/data-and-backend/networking - - https://docs.flutter.dev/data-and-backend/serialization/json - - https://docs.flutter.dev/flutter-for/react-native-devs - - https://docs.flutter.dev/get-started/fundamentals/dart - - https://docs.flutter.dev/get-started/fundamentals/index.html.md - https://docs.flutter.dev/get-started/fundamentals/networking - - https://docs.flutter.dev/learn/pathway/tutorial/http-requests - - https://docs.flutter.dev/release/breaking-changes/asset-manifest-dot-json - - https://docs.flutter.dev/release/breaking-changes/network-policy-ios-android -- name: flutter-working-with-databases - description: Manages local data persistence using SQLite or other database solutions. Use when a Flutter app needs to store, query, or synchronize large amounts of structured data on the device. +- name: flutter-implement-json-serialization + description: Create model classes with `fromJson` and `toJson` methods using + `dart:convert`. Use when manually mapping JSON keys to class properties for + simple data structures. instructions: | - Databases are a crucial component of an application's data layer, which - serves as the source of truth for all application data. This data layer, - also known as the model in MVVM terminology, is the only place where - application data should be updated. It is responsible for consuming data - from external APIs, exposing data to the UI, handling UI events that require - data updates, and sending update requests to external APIs. The data layer - typically consists of repositories and services. - - Repositories are the source of truth for application data and contain logic - related to that data, such as updating data in response to user events or - polling for data from services. They are responsible for presenting local or - remote data and should be the only place where data can be modified. - Repositories transform raw data from services into domain models, which are - data models containing only the information needed by the rest of the app. A - service can be used by any number of repositories, and a repository can use - multiple services. For example, a `BookingRepository` might take an - `ApiClient` service as input to get and update raw data from a server. It's - important that the service is a private member so the UI layer cannot bypass - the repository and call a service directly. In offline-first applications, - repositories combine different local and remote data sources to provide a - single access point for data, regardless of device connectivity. - - Databases are particularly useful for persisting and querying large amounts - of data on a local device, offering faster inserts, updates, and queries - compared to other local persistence solutions. Flutter applications can - utilize SQLite databases through the `sqflite` plugin. To work with SQLite - databases, you need to add the `sqflite` and `path` packages as - dependencies. The `path` package helps define the storage location for the - database on disk. When using SQLite, you typically define a data model - (e.g., a `Dog` data model with `id`, `name`, and `age`), open the database, - create tables (e.g., a `dogs` table), and then perform operations like - inserting, retrieving, updating, and deleting data. The `id` is often used - as the primary key for a table to improve query and update times. - - For managing data persistence, a `TodoRepository` might act as the source of - truth for ToDo items, using a `DatabaseService` internally to access an SQL - database via the `sqflite` package. This `DatabaseService` can also be - implemented using other storage packages like `sqlite3`, `drift`, or cloud - solutions like `firebase_database`. The `TodoRepository` would handle - methods like `fetchTodos()`, `createTodo()`, and `deleteTodo()`, ensuring - the database is open before each request. - - For caching data, various strategies exist: - * **`shared_preferences`**: A Flutter plugin that wraps platform-specific - key-value storage, suitable for small data sizes. - * **File system**: For scenarios where `shared_preferences` is - insufficient, caching data with the device's file system can be - explored. - * **On-device databases**: This is the most robust solution for local data - caching, offering significantly improved performance for large datasets - compared to simple files. Both relational (like SQLite, `drift`) and - non-relational (like `hive_ce`, `isar_community`) databases are - available. - * **Remote Caching**: A lightweight caching system specifically for API - responses. - * **Image Caching**: For images, the `cached_network_image` package can be - used to direct the Flutter app to store images on the file system. - + 1. **Import the `dart:convert` library**: Flutter includes a built-in `dart:convert` library that provides a straightforward JSON encoder and decoder. This library allows you to serialize JSON manually. + 2. **Define a model class**: Create a plain model class, for example, named `User`. This class will contain the properties that correspond to your JSON structure. + 3. **Implement a `fromJson` constructor**: Inside your model class, create a factory constructor, such as `User.fromJson()`, which takes a `Map` structure as input. This constructor is responsible for constructing a new instance of your model class from the JSON map. For example, if you receive a 200 OK response from a server, you can parse the JSON using `jsonDecode(response.body) as Map` and then pass it to your `fromJson` constructor. + 4. **Implement a `toJson` method**: Add a `toJson()` method to your model class. This method converts an instance of your model class into a `Map`. + 5. **Benefits of this approach**: Using model classes with `fromJson` and `toJson` methods provides type safety, autocompletion for fields, and compile-time exceptions. This helps combat issues like typos or incorrect type usage that would otherwise lead to runtime crashes. + 6. **Considerations for `jsonDecode()`**: When using `jsonDecode()`, it returns a `dynamic` type, meaning the types of values are not known until runtime. + 7. **Testing**: In a production application, it is important to ensure that serialization works correctly by having unit tests in place for both the `User.fromJson()` and `User.toJson()` methods. resources: - - https://docs.flutter.dev/app-architecture/case-study/data-layer - - https://docs.flutter.dev/app-architecture/concepts - - https://docs.flutter.dev/app-architecture/design-patterns/key-value-data - - https://docs.flutter.dev/app-architecture/design-patterns/offline-first - - https://docs.flutter.dev/app-architecture/design-patterns/sql - - https://docs.flutter.dev/app-architecture/guide - - https://docs.flutter.dev/app-architecture/recommendations - - https://docs.flutter.dev/cookbook/forms/validation - - https://docs.flutter.dev/cookbook/lists/long-lists - - https://docs.flutter.dev/cookbook/lists/mixed-list + - https://docs.flutter.dev/cookbook/networking/background-parsing - https://docs.flutter.dev/cookbook/networking/fetch-data - - https://docs.flutter.dev/cookbook/persistence/sqlite - - https://docs.flutter.dev/cookbook/testing/unit/mocking - - https://docs.flutter.dev/data-and-backend - - https://docs.flutter.dev/get-started/fundamentals/local-caching - - https://docs.flutter.dev/resources/architectural-overview -- 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. - instructions: | - Caching is a technique for retaining application data to show again at a - future time, rather than repeatedly loading it from a remote server. This - can prevent users from waiting for data to load every time they relaunch an - app. - - All caching strategies involve a three-step operation: checking if the cache - contains the desired data, loading the data if the cache is empty, and then - returning the value in the cache. When the cache already contains the - desired information, it's called a "cache hit". If the cache is empty and - data needs to be loaded from the original source, it's a "cache miss". - - There are different types of caching: - * **Local Caching** involves storing data on the device. - * **In-memory caching** stores data in memory, but this data will not - persist beyond a single user session. - * **Persistent caches** store data on the device's hard drive to - ensure performance benefits on fresh application launches. - * **`shared_preferences`** is a Flutter plugin that uses - platform-specific key-value storage, suitable for small data sizes. - * **File system caching** is an option when `shared_preferences` is - insufficient for larger data. The `path_provider` package can be - used to access common file system locations like temporary - directories (which the system can clear) or documents directories - (which persist until the app is deleted). - * **On-device databases** (relational or non-relational) offer - significantly improved performance for large datasets compared to - simple files. Examples include SQLite, Drift, Hive CE, and Isar - Community. - * **Image caching** can be handled with the `cached_network_image` - package, which uses the file system to store images. Image I/O can - be expensive, so caching provides snapshots of complex hierarchies - for easier rendering in subsequent frames. However, raster cache - entries are expensive to construct and use a lot of GPU memory, so - images should only be cached when absolutely necessary. - * **State restoration** is a pattern where aspects of a user's session, - such as navigation stacks, scroll positions, and form progress, are - persisted. Flutter's built-in state restoration syncs data from its - Element tree with the Flutter engine, which then caches it in - platform-specific storage. - * **FlutterEngine caching** can minimize Flutter's initialization time - when adding Flutter screens to an Android app. By default, each - `FlutterActivity` creates its own `FlutterEngine`, which has a - non-trivial warm-up time. A pre-warmed, cached `FlutterEngine` can be - used instead. - - Caching can also be used to optimize widget rendering. While generally - preferable to rely on caching widgets, comparing properties of leaf widgets - (widgets with no children) can be more efficient than rebuilding them if - they rarely change. However, overriding `operator ==` can lead to - performance degradation. - resources: - - https://docs.flutter.dev/add-to-app/android/add-flutter-fragment/index.html.md - - https://docs.flutter.dev/add-to-app/android/add-flutter-screen - - https://docs.flutter.dev/app-architecture/design-patterns/offline-first - - https://docs.flutter.dev/cookbook/persistence/reading-writing-files - - https://docs.flutter.dev/cookbook/persistence/sqlite - - https://docs.flutter.dev/get-started/fundamentals - - https://docs.flutter.dev/get-started/fundamentals/local-caching - - https://docs.flutter.dev/learn/pathway - - https://docs.flutter.dev/perf/best-practices - - https://docs.flutter.dev/perf/ui-performance - - https://docs.flutter.dev/platform-integration/web/faq - - https://docs.flutter.dev/release/breaking-changes/image-cache-and-provider - - https://docs.flutter.dev/release/breaking-changes/imagecache-large-images - - https://docs.flutter.dev/release/breaking-changes/scroll-cache-extent -- 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. - instructions: | - Platform Views allow you to embed native views into a Flutter application. - This functionality enables the application of transforms, clips, and opacity - to the native view directly from Dart. For example, you can integrate native - Google Maps from the Android SDK or iOS SDK directly into your Flutter app. - Similarly, native web views can be used directly inside a Flutter app on - macOS. - - Platform Views are available for Android, iOS, and macOS. For Android, - Platform Views require API 23+. On Android, there are two implementations - for Platform Views: Hybrid Composition and Texture Layer (or Texture Layer - Hybrid Composition). Hybrid Composition renders Platform Views normally, - with Flutter content rendered into a texture, and SurfaceFlinger composing - both. This offers the best performance and fidelity for Android views but - can cause Flutter performance to suffer, lower application FPS, and prevent - certain transformations on Flutter widgets from working with platform views. - Texture Layer renders Platform Views into a texture, with Flutter drawing - them via the texture and Flutter content rendered directly into a Surface. - This provides good performance for Android Views and best performance for - Flutter rendering, with all transformations working correctly. However, - quick scrolling (e.g., in a web view) can be janky, SurfaceViews are - problematic and moved into a virtual display (breaking accessibility), and - text magnifiers will break unless Flutter is rendered into a TextureView. - Prior to Android 10, hybrid composition copied each Flutter frame out of - graphics memory into main memory and then back to a GPU texture, impacting - overall Flutter UI performance; in Android 10 and above, this copy happens - only once. - - On iOS, only Hybrid Composition is used, where the native `UIView` is - appended to the view hierarchy. Platform views in Flutter come with - performance trade-offs. Techniques to mitigate performance issues for - complex cases include using a placeholder texture during animations in Dart, - such as rendering a screenshot of the native view as a texture if an - animation is slow while a platform view is rendered. There are also - composition limitations for iOS Platform Views, where `ShaderMask` and - `ColorFiltered` widgets are not supported, and `BackdropFilter` has - limitations. For macOS, platform view support is not fully functional as of - the current release, with gesture support not yet available. - - To create a platform view on Android, you need to create a Widget on the - Dart side and add a build implementation. This involves importing necessary - Flutter packages and implementing a `build()` method that specifies a - `viewType` and can pass parameters to the platform side. Handling - `SurfaceViews` is problematic for Flutter and should be avoided when - possible. Certain Android Views, like `SurfaceView` and `SurfaceTexture`, do - not invalidate themselves when their content changes, requiring manual - invalidation by calling `invalidate` on the View or a parent view. - - Flutter can be added to existing web applications, including those using - HTML DOM-based frameworks (Dart or JS), server-side rendered frameworks, or - even "VanillaJS". The minimum requirement is that the existing application - supports importing JavaScript libraries and creating HTML elements for - Flutter to render into. After building Flutter normally, embedding - instructions are followed to place Flutter views on the page. Multi-view - mode is not supported for mobile limitations but can be enabled for web - applications by setting `multiViewEnabled: true` in the `initializeEngine` - method. Flutter views can be managed from JavaScript using an `app` object - returned by `runApp`, allowing for adding and removing views. View additions - and removals are surfaced to Flutter through the `didChangeMetrics` method - of the `WidgetsBinding` class, and the complete list of views is available - via `WidgetsBinding.instance.platformDispatcher.views`. To render content - into each `FlutterView`, the Flutter app needs to create a `View` widget, - which can be grouped under a `ViewCollection` widget. - resources: - - https://docs.flutter.dev/add-to-app - - https://docs.flutter.dev/add-to-app/android/add-flutter-view - - https://docs.flutter.dev/add-to-app/multiple-flutters - - https://docs.flutter.dev/flutter-for/android-devs - - https://docs.flutter.dev/get-started/flutter-for/android-devs - - https://docs.flutter.dev/get-started/flutter-for/swiftui-devs - - https://docs.flutter.dev/platform-integration - - https://docs.flutter.dev/platform-integration/android/platform-views - - https://docs.flutter.dev/platform-integration/android/platform-views?tab=android-platform-views-java-tab - - https://docs.flutter.dev/platform-integration/ios/platform-views - - https://docs.flutter.dev/platform-integration/macos/platform-views - - https://docs.flutter.dev/platform-integration/platform-channels - - https://docs.flutter.dev/platform-integration/web/embedding-flutter-web - - https://docs.flutter.dev/release/breaking-changes/platform-views-using-html-slots-web - - https://docs.flutter.dev/resources/architectural-overview - - https://docs.flutter.dev/resources/faq -- name: flutter-interoperating-with-native-apis - description: Interoperates with native platform APIs in a Flutter app on Android, iOS, and the web. Use when accessing device-specific features not available in Dart or calling existing native code. - instructions: | - Native interop on Android, iOS, and Web involves several methods for - integrating Flutter applications with platform-specific code and features. - - For Android, Flutter allows binding to native C code using the `dart:ffi` - library. This library, which stands for foreign function interface, enables - Flutter mobile and desktop apps to call native C APIs. While a legacy - approach to C interop on Android is documented, a `package_ffi` template - with build hooks is now recommended for C interop since Flutter 3.38. - However, the legacy `plugin_ffi` template remains useful for accessing the - Flutter Plugin API or configuring a Google Play services runtime. Flutter - also supports writing custom platform-specific code for Android using Kotlin - or Java. Messages can be passed between the Flutter UI and the Android - platform using platform channels, with `MethodChannel` for Android receiving - method calls and sending back results. Android developers can leverage their - existing knowledge when building Flutter apps, as Flutter relies on the - mobile operating system for various capabilities. - - On iOS, Flutter supports binding to native C, Objective-C, and Swift code. - Similar to Android, the `dart:ffi` library can be used to call native C - APIs. For C interop on iOS, a `package_ffi` template with build hooks is - recommended since Flutter 3.38, though the `plugin_ffi` template is still - useful for accessing the Flutter Plugin API or using static linking. When - binding to native macOS code, which shares similarities with iOS, the FFI - library can only bind against C symbols, requiring C++ symbols to be marked - `extern "C"`. Attributes like `__attribute__((visibility("default"))) - __attribute__((used))` should be added to prevent the linker from discarding - symbols during link-time optimization. Dynamically linked libraries are - automatically loaded when the app starts, and their symbols can be resolved - using `DynamicLibrary.process`. Flutter also supports writing custom - platform-specific code for iOS using Swift or Objective-C. Platform channels - are used to pass messages asynchronously between the Flutter UI and the iOS - platform, with `FlutterMethodChannel` for iOS handling method calls. Flutter - also supports new and upcoming iOS features and allows embedding native - macOS views. - - For the web, Flutter offers several integration options. This includes - adding custom integrations with the web platform, customizing app - initialization, embedding native web content, and embedding a Flutter app - within another web app. Flutter can compile to WebAssembly (Wasm) to take - advantage of its benefits. However, Flutter compiled to Wasm currently - cannot run on the iOS version of any browser due to WebKit requirements and - compatibility issues. Interoperability with JavaScript is also supported, - allowing integration from Dart code. For web-specific code, JS - interoperability is generally used instead of platform channels. Dart has - introduced new, lightweight interop solutions built around static JS - interop, such as `package:web` (replacing `dart:html`) and `dart:js_interop` - (replacing `package:js` and `dart:js`), to support compilation to Wasm. - - Across all platforms, Flutter allows writing custom platform-specific code - using platform channels. The Pigeon package can be used to generate - type-safe platform-specific code. Messages are passed asynchronously between - the client (UI) and host (platform) to ensure a responsive user interface. - Flutter also supports adding Flutter to an existing app on Android, iOS, and - web. This "add-to-app" feature supports multi-engine on Android and iOS, - allowing multiple isolated Flutter instances, and multi-view on the web, - where multiple `FlutterViews` can share objects within a single Dart - program. Common use cases for add-to-app include hybrid navigation stacks - and partial-screen views. - - resources: - - https://docs.flutter.dev/platform-integration - - https://docs.flutter.dev/platform-integration/android/c-interop - - https://docs.flutter.dev/platform-integration/android/call-jetpack-apis - - https://docs.flutter.dev/platform-integration/android/platform-views - - https://docs.flutter.dev/platform-integration/bind-native-code - - https://docs.flutter.dev/platform-integration/ios - - https://docs.flutter.dev/platform-integration/ios/c-interop - - https://docs.flutter.dev/platform-integration/ios/platform-views - - https://docs.flutter.dev/platform-integration/macos/c-interop - - https://docs.flutter.dev/platform-integration/platform-channels - - https://docs.flutter.dev/platform-integration/web/wasm - - https://docs.flutter.dev/platform-integration/web/web-content-in-flutter - - https://docs.flutter.dev/resources/faq -- 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. - instructions: | - To build a plugin, you first need to create the package. You can create a - plugin package using the `flutter create --template=plugin` command. When - creating the package, you can specify the supported platforms using the - `--platforms=` option, followed by a comma-separated list of platforms such - as `android`, `ios`, `web`, `linux`, `macos`, and `windows`. If no platforms - are specified, the project will not support any platforms. You can also use - the `--org` option to specify your organization using reverse domain name - notation, which is used in package and bundle identifiers. By default, the - plugin project uses Swift for iOS and Kotlin for Android, but you can - specify Objective-C or Java using the `-i` and `-a` flags respectively. - - For FFI plugins, which are useful for accessing the Flutter Plugin API, - configuring Google Play services on Android, or using static linking on iOS - or macOS, you would use `flutter create --template=plugin_ffi`. This creates - an FFI plugin project with Dart code in `lib` that defines the API and calls - native code using `dart:ffi`, and native source code in `src` with a - `CMakeLists.txt` file for building a dynamic library. FFI plugin packages - support bundling native code and method channel registration code, but not - method channels themselves. If you need to use both method channels and FFI, - you should use a non-FFI plugin. Each platform can use either an FFI or - non-FFI plugin. - - After creating the package, you need to implement it. The API of the plugin - package is defined in Dart code, typically in `lib/.dart`. For - Android platform code, it's recommended to edit it using Android Studio. You - should build the code at least once before editing, then open the - `hello/example/android/build.gradle` or - `hello/example/android/build.gradle.kts` file in Android Studio. The Android - platform code for your plugin is located in - `hello/java/com.example.hello/HelloPlugin`. For Windows, you can open the - `hello/example/build/windows/hello_example.sln` file in Visual Studio, and - the Windows platform code is in `hello_plugin/Source Files` and - `hello_plugin/Header Files`. After making changes to plugin code, you must - rebuild the solution. - - You also need to connect the Dart API with the platform-specific - implementations using a platform channel or through interfaces defined in a - platform interface package. To add support for specific platforms to an - existing plugin project, you can run `flutter create --template=plugin` - again in the project directory with the `--platforms=` flag. You might also - need to update the `podspec` file to set dependencies and deployment targets - for iOS and macOS. - - Federated plugins are a way to split a plugin's API into a platform - interface, independent platform implementations, and an app-facing - interface. This allows different teams with different expertise to build - plugin implementations for various platforms. A federated plugin requires an - app-facing interface for users, one or more platform implementations for - platform-specific code, and a platform interface that connects the - app-facing interface to the implementations. Package-separated federated - plugins separate these components into their own Dart packages. You can add - new platform implementations to endorsed federated plugins on pub.dev by - coordinating with the original plugin author. - - For Android plugins, if possible, move logic from `registerWith()` into a - private method that both `registerWith()` and `onAttachedToEngine()` can - call, as only one will be called. Document all non-overridden public - members. If your plugin needs an `Activity` reference, implement the - `ActivityAware` interface. If it's expected to be held in a background - `Service`, implement `ServiceAware`. Update the example app's - `MainActivity.java` to use the v2 embedding `FlutterActivity`. You might - also need a public constructor for your plugin class. - - resources: - - https://docs.flutter.dev/add-to-app/android/plugin-setup - - https://docs.flutter.dev/packages-and-plugins/developing-packages - - https://docs.flutter.dev/packages-and-plugins/swift-package-manager/for-plugin-authors - - https://docs.flutter.dev/packages-and-plugins/using-packages - - https://docs.flutter.dev/platform-integration/desktop - - https://docs.flutter.dev/platform-integration/ios/apple-frameworks - - https://docs.flutter.dev/platform-integration/platform-channels - - https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply - - https://docs.flutter.dev/release/breaking-changes/plugin-api-migration - - https://docs.flutter.dev/testing/plugins-in-tests - - https://docs.flutter.dev/testing/testing-plugins - - https://docs.flutter.dev/tools/android-studio -- name: flutter-testing-apps - description: Implements unit, widget, and integration tests for a Flutter app. Use when ensuring code quality and preventing regressions through automated testing. - instructions: | - Automated testing is crucial for ensuring an app performs correctly before - publication and for maintaining feature and bug fix velocity, especially as - an app gains more features, making manual testing harder. Good testing - practices contribute to app flexibility, making it straightforward and - low-risk to add new logic and UI. Testing is a key aspect of software - development, encompassing various types of tests and considerations for - different platforms and architectural components. - - Automated testing generally falls into three main categories: unit tests, - widget tests, and integration tests. A well-tested app typically includes - many unit and widget tests, with code coverage tracking, and enough - integration tests to cover important use cases. There are trade-offs between - these different kinds of testing regarding confidence, maintenance cost, - dependencies, and execution speed. - - Unit tests focus on verifying the correctness of a single function, method, - or class. The goal is to test a unit of logic under various conditions, - often by mocking out external dependencies. Unit tests generally do not - involve disk I/O, screen rendering, or user actions from outside the test - process. For architectural components, it is strongly recommended to write - unit tests for every service, repository, and ViewModel class, testing the - logic of each method individually. - - Widget tests, also known as component tests in other UI frameworks, test a - single widget. The purpose of a widget test is to ensure the widget's UI - looks and interacts as expected. Testing a widget involves multiple classes - and requires a test environment that provides the appropriate widget - lifecycle context. Widget tests should be written for views. - - Integration tests verify the behavior of a complete app or a large part of - an app. These tests can also be referred to as end-to-end testing or GUI - testing. Integration tests are used to validate how individual pieces of an - app work together and to capture the performance of an app running on a real - device, which unit and widget tests do not cover. The Flutter SDK includes - the `integration_test` package for running these tests. Integration tests - are particularly important for testing routing and dependency injection. To - run integration tests, the `integration_test` package needs to be added as a - dependency for the Flutter app's test file. Integration tests can be run - from the command line or a CI system for desktop platforms, from the command - line or Firebase Test Lab for mobile platforms, and from the command line - for web browsers. For Linux apps, a CI system must invoke an X server, often - using the `xvfb-run` tool. - - When testing plugins, all usual types of Flutter tests apply, but due to - native code, plugins often require additional types of tests. Dart unit - tests and widget tests can be used to test the Dart portion of a plugin. - Native unit tests are also relevant. For Android, JUnit tests are configured - in `android/src/test/`. For iOS and macOS, XCTest tests are found in - `example/ios/RunnerTests/` and `example/macos/RunnerTests/` respectively, - run via the example app's project. For Linux and Windows, GoogleTest tests - are located in `linux/test/` and `windows/test/` respectively. Native UI - tests, such as Espresso or XCUITest, can be useful if a plugin cannot be - tested without native UI interactions, as they interact with both native and - Flutter UI elements. Since only integration tests can test communication - between Dart and native languages, it's recommended to have at least one - integration test for each platform channel call. If certain flows, like - those requiring native UI interaction or mocking device state, cannot be - tested with integration tests, "end-to-end" tests can be implemented using - native unit tests that call into the method channel entry point with a - synthesized call, and Dart unit tests that mock the platform channel and - call the plugin's public API. - - Testing architectural components separately and together is a strong - recommendation. Observability and testability are important architectural - principles, advocating for building in logging, metrics, tracing, and - ensuring components can be unit and integration tested. - resources: - - https://docs.flutter.dev/app-architecture/case-study/testing - - https://docs.flutter.dev/app-architecture/concepts - - https://docs.flutter.dev/app-architecture/recommendations - - https://docs.flutter.dev/cookbook/testing/integration - - https://docs.flutter.dev/cookbook/testing/integration/introduction - - https://docs.flutter.dev/cookbook/testing/unit/introduction - - https://docs.flutter.dev/cookbook/testing/widget/finders - - https://docs.flutter.dev/cookbook/testing/widget/introduction - - https://docs.flutter.dev/resources/faq - - https://docs.flutter.dev/testing - - https://docs.flutter.dev/testing/integration-tests - - https://docs.flutter.dev/testing/overview - - https://docs.flutter.dev/testing/testing-plugins -- name: flutter-reducing-app-size - description: Measures and optimizes the size of Flutter application bundles for deployment. Use when minimizing download size or meeting app store package constraints. - instructions: | - To effectively reduce app size, an agent needs to understand how to measure - app size, analyze its components, and implement specific reduction - strategies. - - **Measuring and Analyzing App Size:** - The size of a compiled app, whether an APK, app bundle, or IPA, is a - significant concern because larger apps require more device space, take - longer to download, and can exceed limits for features like Android instant - apps. Debug builds are not representative of the final app size. To measure - app size, the `--analyze-size` flag can be used with `flutter build` - commands for various platforms like `apk`, `appbundle`, `ios`, `linux`, - `macos`, and `windows`. This build process compiles Dart in a way that - records code size usage of Dart packages and provides a high-level summary - in the terminal. It also generates a `*-code-size-analysis_*.json` file for - more detailed analysis in DevTools. - - For iOS, a `.app` file is created, which is useful for evaluating the - relative size of its content, but for a closer estimate of download size, - the "Estimating total size" section should be referenced. An upload package - is not always representative of end-users' download size, as redundant - native library architectures and asset densities can be filtered by app - stores. End users' download sizes can also vary based on their hardware. - - Deeper analysis can be performed in DevTools by uploading the - `*-code-size-analysis_*.json` file. The DevTools app size tool allows for a - tree or treemap view to break down application contents to individual file - and function levels for the Dart AOT artifact. This tool can be accessed by - running `dart devtools`, selecting "Open app size tool," and uploading the - JSON file. DevTools can also compare two builds by loading two - `*-code-size-analysis_*.json` files. The "size information" file contains - data for the total application size. The Dart AOT compiler performs - tree-shaking in profile or release modes, optimizing app size by removing - unused or unreachable code. The result is a collection of packages, - libraries, classes, and functions in the binary output, along with their - sizes in bytes, which can be analyzed in the app size tool. - - For iOS, an Xcode App Size Report can be created by configuring the app - version and build, then running `flutter build ipa --export-method - development`. Opening the archive in Xcode, selecting "Distribute App" - (development is simplest), choosing "all compatible device variants" in App - Thinning, and stripping Swift symbols, allows for signing and exporting the - IPA. The exported directory will contain `App Thinning Size Report.txt` with - projected application sizes for different devices and iOS versions. - - **Strategies for App Size Reduction:** - When building a release version of an app, using the `--split-debug-info` - tag can significantly reduce code size. Other methods to make an app smaller - include removing unused resources, minimizing resources imported from - libraries, and compressing PNG and JPEG files. - resources: - - https://docs.flutter.dev/ai-best-practices - - https://docs.flutter.dev/deployment/ios - - https://docs.flutter.dev/perf - - https://docs.flutter.dev/perf/app-size - - https://docs.flutter.dev/perf/best-practices - - https://docs.flutter.dev/platform-integration/ios/ios-app-clip - - https://docs.flutter.dev/platform-integration/windows/building - - https://docs.flutter.dev/resources/faq - - https://docs.flutter.dev/tools/devtools/app-size - - https://docs.flutter.dev/tools/devtools/memory - - https://docs.flutter.dev/tools/devtools/performance - - https://docs.flutter.dev/tools/sdk - - https://docs.flutter.dev/ui/adaptive-responsive - - https://docs.flutter.dev/ui/adaptive-responsive/best-practices - - https://docs.flutter.dev/ui/adaptive-responsive/general - - https://docs.flutter.dev/ui/adaptive-responsive/large-screens -- name: flutter-handling-concurrency - description: Executes long-running tasks in background isolates in a Flutter app to keep the UI responsive. Use when performing heavy computations or parsing large datasets. - instructions: | - Dart has a single-threaded execution model, which includes support for - Isolates, an event loop, and asynchronous code. An Isolate is Dart's - implementation of a lightweight thread. All Dart code runs in isolates, - which are similar to threads but have their own isolated memory and do not - share state. Isolates can only communicate by messaging. By default, Flutter - applications perform all their work on a single isolate, known as the main - isolate. This model is often sufficient for simpler programming and to keep - the application's UI responsive. - - Unless an Isolate is explicitly spawned, Dart code runs in the main UI - thread, which is driven by an event loop. Flutter's event loop is comparable - to the iOS main loop. Dart's single-threaded model does not necessitate - running all operations as blocking tasks that would freeze the UI. Instead, - Dart provides asynchronous features like `async` and `await`. An - asynchronous operation allows other operations to execute before it - completes. The `async` keyword indicates that a function performs - asynchronous work, and `await` tells the system to wait for a result from - that function. - - In Dart, a `Future` object represents a value that will be provided in the - future. Functions marked as `async` automatically return a `Future`. Flutter - uses `FutureBuilder` and `StreamBuilder` widgets to display the results of a - `Future` in the UI. - - For long-running operations that might block UI rendering, especially on - multi-core hardware, tasks can be offloaded to a worker isolate, also known - as a background worker. As of Dart 2.19, `Isolate.run()` can be used to - spawn an isolate and execute computations. This method spawns an isolate, - passes a callback for computation, returns the result, and then shuts down - the isolate upon completion, all while running concurrently with the main - isolate without blocking it. The `Isolate.run` method requires a single - argument, which is a callback function that runs on the new isolate. This - callback's function signature must have exactly one required, unnamed - argument. When the computation finishes, its value is returned to the main - isolate, and the spawned isolate exits. An example use case is decoding a - large JSON blob, which, if not offloaded, could make the UI unresponsive. - - For long-lived isolates that need to pass multiple messages over time, the - `Isolate` API and Ports can be used. These are useful for processes that run - repeatedly or over a period, yielding multiple return values to the main - isolate. Communication between isolates is exclusively through `ReceivePort` - and `SendPort`. `SendPort` acts like a `StreamController` for sending - messages, while `ReceivePort` functions as a listener, calling a provided - callback when a new message is received. - resources: - - https://docs.flutter.dev/add-to-app/performance + - https://docs.flutter.dev/cookbook/networking/send-data + - https://docs.flutter.dev/cookbook/networking/update-data + - https://docs.flutter.dev/data-and-backend/serialization - https://docs.flutter.dev/data-and-backend/serialization/json - - https://docs.flutter.dev/data-and-backend/state-mgmt/options - - https://docs.flutter.dev/flutter-for/dart-swift-concurrency - - https://docs.flutter.dev/flutter-for/react-native-devs - - https://docs.flutter.dev/flutter-for/uikit-devs - - https://docs.flutter.dev/get-started/flutter-for/android-devs - - https://docs.flutter.dev/get-started/flutter-for/dart-swift-concurrency - - https://docs.flutter.dev/get-started/flutter-for/uikit-devs - - https://docs.flutter.dev/get-started/fundamentals/dart - - https://docs.flutter.dev/perf/isolates - - https://docs.flutter.dev/platform-integration/web/faq - - https://docs.flutter.dev/resources/architectural-overview - - https://docs.flutter.dev/resources/bootstrap-into-dart - - https://docs.flutter.dev/resources/faq -- 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. +- name: flutter-accessibility-audit + description: Triggers an accessibility scan through the widget_inspector and + automatically adds Semantics widgets or missing labels to the source code. instructions: | - Home screen widgets allow users to see and interact with your app's data - directly from their device's home screen. Adding a home screen widget to a - Flutter app involves using native frameworks for each platform (SwiftUI for - iOS and XML for Android) to create the widget's UI, while sharing data and - resources between the Flutter app and the native components. + 1. **Understand Semantics and Accessibility** + * Semantic roles define the purpose of a UI element, helping screen readers and other assistive tools interpret and present your application to users. + * Flutter's standard widgets often provide semantics automatically. + * For custom components or when default semantics are insufficient, you can use the `Semantics` widget to define the role. + * The `Semantics` widget with the `SemanticsRole` enum allows developers to assign specific roles to their widgets. + * When a Flutter web app is rendered, Flutter-specific roles are translated into corresponding ARIA roles in the web page's HTML structure. + * Flutter needs a special layer to expose the UI's meaning and structure to web browsers because it renders its UI on a single canvas. + * For web accessibility, Flutter's web accessibility is not on by default for performance reasons. Users can turn it on by pressing an invisible button with `aria-label="Enable accessibility"`. Alternatively, you can enable accessibility in code by adding `SemanticsBinding.instance.ensureSemantics();` within your `main()` function if `kIsWeb` is true. + + 2. **Initiate an Accessibility Scan** + * **For Android:** Install the Accessibility Scanner for Android and enable it from Android Settings > Accessibility > Accessibility Scanner > On. Then, navigate to the Accessibility Scanner 'checkbox' icon button to initiate a scan. + * **For iOS:** Open the iOS folder of your Flutter app in Xcode. Select a Simulator as the target and click the Run button. In Xcode, select Xcode > Open Developer Tools > Accessibility Inspector. In the Accessibility Inspector, select Inspection > Enable Point to Inspect, and then select UI elements in your running Flutter app to inspect their accessibility attributes. To get a report of potential issues, select Audit in the toolbar and then Run Audit. + * **For Web:** Open Chrome DevTools (or similar tools in other browsers). Inspect the HTML tree under `semantics host`, which contains the ARIA attributes generated by Flutter. In Chrome, the "Elements" tab has an "Accessibility" sub-tab to inspect data exported to the semantics tree. You can also debug accessibility by visualizing semantic nodes for your web app using the command line flag `flutter run -d chrome --profile --dart-define=FLUTTER_WEB_DEBUG_SHOW_SEMANTICS=true` in profile and release modes. With this flag activated, semantic nodes appear on top of widgets, allowing you to verify their placement. + * **Using Flutter's Accessibility Guideline API:** Test your app using Flutter's Accessibility Guideline API, which checks if your app's UI meets Flutter's accessibility recommendations for text contrast, target size, and target labels. You can add Guideline API tests alongside other widget tests or in a separate file, such as `test/a11y_test.dart`. + + 3. **Identify and Address Accessibility Issues** + * The `Semantics` tree is presented to the system accessibility APIs. + * To obtain a dump of the `Semantics` tree, enable accessibility using a system accessibility tool or the `SemanticsDebugger`, and then use the `debugDumpSemanticsTree()` function. + * You can call `debugDumpSemanticsTree()` from a `GestureDetector`'s `onTap` callback, for example. + * If semantic nodes are incorrectly placed, you should file a bug report. - Common approaches involve using the `home_widget` package to facilitate - communication between the Flutter app and the native widget. On iOS, this - typically involves creating a Widget Extension target in Xcode and using - App Groups to share data. On Android, you create an `AppWidgetProvider` - and use `SharedPreferences` for data sharing. Key techniques include - sending data updates to the widget, using custom fonts, and even - rendering Flutter widgets as images for display in the native widget UI. resources: - - https://codelabs.developers.google.com/flutter-home-screen-widgets#0 - - https://pub.dev/packages/home_widget \ No newline at end of file + - https://docs.flutter.dev/ui/accessibility + - https://docs.flutter.dev/ui/accessibility/accessibility-testing + - https://docs.flutter.dev/ui/accessibility/assistive-technologies + - https://docs.flutter.dev/ui/accessibility/web-accessibility + - https://docs.flutter.dev/ui/accessibility/web-accessibility + - https://docs.flutter.dev/ui/widgets/accessibility \ No newline at end of file diff --git a/skills/flutter-accessibility-audit/SKILL.md b/skills/flutter-accessibility-audit/SKILL.md new file mode 100644 index 0000000..19bedac --- /dev/null +++ b/skills/flutter-accessibility-audit/SKILL.md @@ -0,0 +1,147 @@ +--- +name: flutter-accessibility-audit +description: Triggers an accessibility scan through the widget_inspector and automatically adds Semantics widgets or missing labels to the source code. +metadata: + model: models/gemini-3.1-pro-preview + last_modified: Tue, 21 Apr 2026 21:57:55 GMT +--- +# Implementing Flutter Accessibility + +## Contents +- [Managing Semantics](#managing-semantics) +- [Auditing Accessibility](#auditing-accessibility) +- [Debugging the Semantics Tree](#debugging-the-semantics-tree) +- [Examples](#examples) + +## Managing Semantics + +Rely on Flutter's standard widgets (e.g., `TabBar`, `MenuAnchor`) for automatic semantic role assignment whenever possible. When building custom components or overriding default behaviors, explicitly define the UI element's purpose using the `Semantics` widget. + +* Wrap custom UI components in a `Semantics` widget. +* Assign the appropriate `SemanticsRole` enum value to the `role` property to define the element's purpose (e.g., button, list, heading). +* If building for Flutter Web, note that Flutter translates these roles into corresponding ARIA roles in the HTML DOM. +* Enable web accessibility explicitly. It is disabled by default for performance. Either instruct users to press the invisible `aria-label="Enable accessibility"` button, or force it programmatically in your `main()` function. + +## Auditing Accessibility + +Implement the following workflows to verify that your application meets accessibility standards. + +### Task Progress: Platform-Specific Scanning +Copy this checklist to track your manual auditing progress across target platforms: + +- [ ] **If testing on Android:** + 1. Install the Accessibility Scanner from Google Play. + 2. Enable it via **Settings > Accessibility > Accessibility Scanner > On**. + 3. Tap the Accessibility Scanner checkmark icon over your running app to initiate the scan. +- [ ] **If testing on iOS:** + 1. Open the `ios` folder in Xcode and run the app on a Simulator. + 2. Navigate to **Xcode > Open Developer Tools > Accessibility Inspector**. + 3. Select **Inspection > Enable Point to Inspect** and click UI elements to verify attributes. + 4. Select **Audit > Run Audit** to generate an issue report. +- [ ] **If testing on Web:** + 1. Open Chrome DevTools. + 2. Inspect the HTML tree under the `semantics host` node. + 3. Navigate to the **Elements** tab and open the **Accessibility** sub-tab to inspect exported ARIA data. + 4. Visualize semantic nodes by running the app with: `flutter run -d chrome --profile --dart-define=FLUTTER_WEB_DEBUG_SHOW_SEMANTICS=true`. + +### Task Progress: Automated Testing +Integrate Flutter's Accessibility Guideline API into your widget tests to catch contrast, target size, and labeling issues automatically. + +- [ ] Create a dedicated test file (e.g., `test/a11y_test.dart`). +- [ ] Initialize the semantics handle using `tester.ensureSemantics()`. +- [ ] Assert against `androidTapTargetGuideline` (48x48px minimum). +- [ ] Assert against `iOSTapTargetGuideline` (44x44px minimum). +- [ ] Assert against `labeledTapTargetGuideline`. +- [ ] Assert against `textContrastGuideline` (3:1 minimum for large text). +- [ ] Dispose of the semantics handle at the end of the test. + +## Debugging the Semantics Tree + +When semantic nodes are incorrectly placed or missing, execute the following feedback loop to identify and resolve the discrepancies. + +1. **Run validator:** Trigger a dump of the Semantics tree to the console. + * Enable accessibility via a system tool or `SemanticsDebugger`. + * Invoke `debugDumpSemanticsTree()` (e.g., bind it to a `GestureDetector`'s `onTap` callback for easy triggering during debugging). +2. **Review errors:** Analyze the console output to locate missing labels, incorrect roles, or improperly nested semantic nodes. +3. **Fix:** Wrap the offending widgets in `Semantics` or `MergeSemantics` widgets, apply the correct `SemanticsRole`, and repeat step 1 until the tree accurately reflects the visual UI. + +## Examples + +### Programmatically Enabling Web Accessibility +Force the Semantics tree to build immediately on Flutter Web. + +```dart +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/semantics.dart'; + +void main() { + runApp(const MyApp()); + if (kIsWeb) { + SemanticsBinding.instance.ensureSemantics(); + } +} +``` + +### Explicitly Defining Semantic Roles +Assign explicit list and list-item roles to a custom layout. + +```dart +import 'package:flutter/material.dart'; +import 'package:flutter/semantics.dart'; + +class MyCustomListWidget extends StatelessWidget { + const MyCustomListWidget({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Semantics( + role: SemanticsRole.list, + explicitChildNodes: true, + child: Column( + children: [ + Semantics( + role: SemanticsRole.listItem, + child: const Padding( + padding: EdgeInsets.all(8.0), + child: Text('Content of the first custom list item.'), + ), + ), + Semantics( + role: SemanticsRole.listItem, + child: const Padding( + padding: EdgeInsets.all(8.0), + child: Text('Content of the second custom list item.'), + ), + ), + ], + ), + ); + } +} +``` + +### Automated Accessibility Testing +Implement the Accessibility Guideline API in a widget test. + +```dart +import 'package:flutter_test/flutter_test.dart'; +import 'package:your_accessible_app/main.dart'; + +void main() { + testWidgets('Follows a11y guidelines', (tester) async { + final SemanticsHandle handle = tester.ensureSemantics(); + await tester.pumpWidget(const AccessibleApp()); + + // Check tap target sizes + await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); + await expectLater(tester, meetsGuideline(iOSTapTargetGuideline)); + + // Check labels and contrast + await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); + await expectLater(tester, meetsGuideline(textContrastGuideline)); + + handle.dispose(); + }); +} +``` diff --git a/skills/flutter-add-integration-test/SKILL.md b/skills/flutter-add-integration-test/SKILL.md new file mode 100644 index 0000000..60902f1 --- /dev/null +++ b/skills/flutter-add-integration-test/SKILL.md @@ -0,0 +1,163 @@ +--- +name: flutter-add-integration-test +description: Configures Flutter Driver for app interaction and converts MCP actions into permanent integration tests. Use when adding integration testing to a project, exploring UI components via MCP, or automating user flows with the integration_test package. +metadata: + model: models/gemini-3.1-pro-preview + last_modified: Tue, 21 Apr 2026 18:29:20 GMT +--- +# Implementing Flutter Integration Tests + +## Contents +- [Project Setup and Dependencies](#project-setup-and-dependencies) +- [Interactive Exploration via MCP](#interactive-exploration-via-mcp) +- [Test Authoring Guidelines](#test-authoring-guidelines) +- [Execution and Profiling](#execution-and-profiling) +- [Workflow: End-to-End Integration Testing](#workflow-end-to-end-integration-testing) +- [Examples](#examples) + +## Project Setup and Dependencies + +Configure the project to support integration testing and Flutter Driver extensions. + +1. Add required development dependencies to `pubspec.yaml`: + ```bash + flutter pub add 'dev:integration_test:{"sdk":"flutter"}' + flutter pub add 'dev:flutter_test:{"sdk":"flutter"}' + ``` +2. Enable the Flutter Driver extension in your application entry point (typically `lib/main.dart` or a dedicated `lib/main_test.dart`): + - Import `package:flutter_driver/driver_extension.dart`. + - Call `enableFlutterDriverExtension();` before `runApp()`. +3. Add `Key` parameters (e.g., `ValueKey('login_button')`) to critical widgets in the application code to ensure reliable targeting during tests. + +## Interactive Exploration via MCP + +Use the Dart/Flutter MCP server tools to interactively explore and manipulate the application state before writing static tests. + +- **Launch**: Execute `launch_app` with `target: "lib/main_test.dart"` to start the application and acquire the DTD URI. +- **Inspect**: Execute `get_widget_tree` to discover available `Key`s, `Text` nodes, and widget `Type`s. +- **Interact**: Execute `tap`, `enter_text`, and `scroll` to simulate user flows. +- **Wait**: Always execute `waitFor` or verify state with `get_health` when navigating or triggering animations. +- **Troubleshoot Unmounted Widgets**: If a widget is not found in the tree, it may be lazily loaded in a `SliverList` or `ListView`. Execute `scroll` or `scrollIntoView` to force the widget to mount before interacting with it. + +## Test Authoring Guidelines + +Structure integration tests using the `flutter_test` API paradigm. + +- Create a dedicated `integration_test/` directory at the project root. +- Name all test files using the `_test.dart` convention. +- Initialize the binding by calling `IntegrationTestWidgetsFlutterBinding.ensureInitialized();` at the start of `main()`. +- Load the application UI using `await tester.pumpWidget(MyApp());`. +- Trigger frames and wait for animations to complete using `await tester.pumpAndSettle();` after interactions like `tester.tap()`. +- Assert widget visibility using `expect(find.byKey(ValueKey('foo')), findsOneWidget);` or `findsNothing`. +- Scroll to specific off-screen widgets using `await tester.scrollUntilVisible(itemFinder, 500.0, scrollable: listFinder);`. + +**Conditional Logic for Legacy `flutter_driver`:** +- If maintaining or migrating legacy `flutter_driver` tests, use `driver.waitFor()`, `driver.waitForAbsent()`, `driver.tap()`, and `driver.scroll()` instead of the `WidgetTester` APIs. + +## Execution and Profiling + +Execute tests using the `flutter drive` command. Require a host driver script located in `test_driver/integration_test.dart` that calls `integrationDriver()`. + +**Conditional Execution Targets:** +- **If testing on Chrome:** Launch `chromedriver --port=4444` in a separate terminal, then run: + `flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart -d chrome` +- **If testing headless web:** Run with `-d web-server`. +- **If testing on Android (Local):** Run `flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart`. +- **If testing on Firebase Test Lab (Android):** + 1. Build debug APK: `flutter build apk --debug` + 2. Build test APK: `./gradlew app:assembleAndroidTest` + 3. Upload both APKs to the Firebase Test Lab console. + +## Workflow: End-to-End Integration Testing + +Copy and follow this checklist to implement and verify integration tests. + +- [ ] **Task Progress: Setup** + - [ ] Add `integration_test` and `flutter_test` to `pubspec.yaml`. + - [ ] Inject `enableFlutterDriverExtension()` into the app entry point. + - [ ] Assign `ValueKey`s to target widgets. +- [ ] **Task Progress: Exploration** + - [ ] Run `launch_app` via MCP. + - [ ] Map the widget tree using `get_widget_tree`. + - [ ] Validate interaction paths using MCP tools (`tap`, `enter_text`). +- [ ] **Task Progress: Authoring** + - [ ] Create `integration_test/app_test.dart`. + - [ ] Write test cases using `WidgetTester` APIs. + - [ ] Create `test_driver/integration_test.dart` with `integrationDriver()`. +- [ ] **Task Progress: Execution & Feedback Loop** + - [ ] Run `flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart`. + - [ ] **Feedback Loop**: Review test output -> If `PumpAndSettleTimedOutException` occurs, check for infinite animations -> If widget not found, add `scrollUntilVisible` -> Re-run test until passing. + +## Examples + +### Standard Integration Test (`integration_test/app_test.dart`) + +```dart +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:my_app/main.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('End-to-end test', () { + testWidgets('tap on the floating action button, verify counter', (tester) async { + // Load app widget. + await tester.pumpWidget(const MyApp()); + + // Verify the counter starts at 0. + expect(find.text('0'), findsOneWidget); + + // Find the floating action button to tap on. + final fab = find.byKey(const ValueKey('increment')); + + // Emulate a tap on the floating action button. + await tester.tap(fab); + + // Trigger a frame and wait for animations. + await tester.pumpAndSettle(); + + // Verify the counter increments by 1. + expect(find.text('1'), findsOneWidget); + }); + }); +} +``` + +### Host Driver Script (`test_driver/integration_test.dart`) + +```dart +import 'package:integration_test/integration_test_driver.dart'; + +Future main() => integrationDriver(); +``` + +### Performance Profiling Driver Script (`test_driver/perf_driver.dart`) + +Use this driver script if you wrap your test actions in `binding.traceAction()` to capture performance metrics. + +```dart +import 'package:flutter_driver/flutter_driver.dart' as driver; +import 'package:integration_test/integration_test_driver.dart'; + +Future main() { + return integrationDriver( + responseDataCallback: (data) async { + if (data != null) { + final timeline = driver.Timeline.fromJson( + data['scrolling_timeline'] as Map, + ); + + final summary = driver.TimelineSummary.summarize(timeline); + + await summary.writeTimelineToFile( + 'scrolling_timeline', + pretty: true, + includeSummary: true, + ); + } + }, + ); +} +``` diff --git a/skills/flutter-add-widget-preview/SKILL.md b/skills/flutter-add-widget-preview/SKILL.md new file mode 100644 index 0000000..030422b --- /dev/null +++ b/skills/flutter-add-widget-preview/SKILL.md @@ -0,0 +1,145 @@ +--- +name: flutter-add-widget-preview +description: Adds interactive widget previews to the project using the previews.dart system. Use when creating new UI components or updating existing screens to ensure consistent design and interactive testing. +metadata: + model: models/gemini-3.1-pro-preview + last_modified: Tue, 21 Apr 2026 20:05:23 GMT +--- +# Previewing Flutter Widgets + +## Contents +- [Preview Guidelines](#preview-guidelines) +- [Handling Limitations](#handling-limitations) +- [Workflows](#workflows) +- [Examples](#examples) + +## Preview Guidelines + +Use the Flutter Widget Previewer to render widgets in real-time, isolated from the full application context. + +- **Target Elements:** Apply the `@Preview` annotation to top-level functions, static methods within a class, or public widget constructors/factories that have no required arguments and return a `Widget` or `WidgetBuilder`. +- **Imports:** Always import `package:flutter/widget_previews.dart` to access the preview annotations. +- **Custom Annotations:** Extend the `Preview` class to create custom annotations that inject common properties (e.g., themes, wrappers) across multiple widgets. +- **Multiple Configurations:** Apply multiple `@Preview` annotations to a single target to generate multiple preview instances. Alternatively, extend `MultiPreview` to encapsulate common multi-preview configurations. +- **Runtime Transformations:** Override the `transform()` method in custom `Preview` or `MultiPreview` classes to modify preview configurations dynamically at runtime (e.g., generating names based on dynamic values, which is impossible in a `const` context). + +## Handling Limitations + +Adhere to the following constraints when authoring previewable widgets, as the Widget Previewer runs in a web environment: + +- **No Native APIs:** Do not use native plugins or APIs from `dart:io` or `dart:ffi`. Widgets with transitive dependencies on `dart:io` will throw exceptions upon invocation, and `dart:ffi` dependencies will fail to load. Use conditional imports to mock or bypass these in preview mode. +- **Asset Paths:** Use package-based paths for assets loaded via `dart:ui` `fromAsset` APIs (e.g., `packages/my_package_name/assets/my_image.png` instead of `assets/my_image.png`). +- **Public Callbacks:** Ensure all callback arguments provided to preview annotations are public and constant to satisfy code generation requirements. +- **Constraints:** Apply explicit constraints using the `size` parameter in the `@Preview` annotation if your widget is unconstrained, as the previewer defaults to constraining them to approximately half the viewport. + +## Workflows + +### Creating a Widget Preview +Copy and track this checklist when implementing a new widget preview: + +- [ ] Import `package:flutter/widget_previews.dart`. +- [ ] Identify a valid target (top-level function, static method, or parameter-less public constructor). +- [ ] Apply the `@Preview` annotation to the target. +- [ ] Configure preview parameters (`name`, `group`, `size`, `theme`, `brightness`, etc.) as needed. +- [ ] If applying the same configuration to multiple widgets, extract the configuration into a custom class extending `Preview`. + +### Interacting with Previews +Follow the appropriate conditional workflow to launch and interact with the Widget Previewer: + +**If using a supported IDE (Android Studio, IntelliJ, VS Code with Flutter 3.38+):** +1. Launch the IDE. The Widget Previewer starts automatically. +2. Open the "Flutter Widget Preview" tab in the sidebar. +3. Toggle "Filter previews by selected file" at the bottom left if you want to view previews outside the currently active file. + +**If using the Command Line:** +1. Navigate to the Flutter project's root directory. +2. Run `flutter widget-preview start`. +3. View the automatically opened Chrome environment. + +**Feedback Loop: Preview Iteration** +1. Modify the widget code or preview configuration. +2. Observe the automatic update in the Widget Previewer. +3. If global state (e.g., static initializers) was modified: Click the global hot restart button at the bottom right. +4. If only the local widget state needs resetting: Click the individual hot restart button on the specific preview card. +5. Review errors in the IDE/CLI console -> fix -> repeat. + +## Examples + +### Basic Preview +```dart +import 'package:flutter/widget_previews.dart'; +import 'package:flutter/material.dart'; + +@Preview(name: 'My Sample Text', group: 'Typography') +Widget mySampleText() { + return const Text('Hello, World!'); +} +``` + +### Custom Preview with Runtime Transformation +```dart +import 'package:flutter/widget_previews.dart'; +import 'package:flutter/material.dart'; + +final class TransformativePreview extends Preview { + const TransformativePreview({ + super.name, + super.group, + }); + + PreviewThemeData _themeBuilder() { + return PreviewThemeData( + materialLight: ThemeData.light(), + materialDark: ThemeData.dark(), + ); + } + + @override + Preview transform() { + final originalPreview = super.transform(); + final builder = originalPreview.toBuilder(); + + builder + ..name = 'Transformed - ${originalPreview.name}' + ..theme = _themeBuilder; + + return builder.toPreview(); + } +} + +@TransformativePreview(name: 'Custom Themed Button') +Widget myButton() => const ElevatedButton(onPressed: null, child: Text('Click')); +``` + +### MultiPreview Implementation +```dart +import 'package:flutter/widget_previews.dart'; +import 'package:flutter/material.dart'; + +/// Creates light and dark mode previews automatically. +final class MultiBrightnessPreview extends MultiPreview { + const MultiBrightnessPreview({required this.name}); + + final String name; + + @override + List get previews => const [ + Preview(brightness: Brightness.light), + Preview(brightness: Brightness.dark), + ]; + + @override + List transform() { + final previews = super.transform(); + return previews.map((preview) { + final builder = preview.toBuilder() + ..group = 'Brightness' + ..name = '$name - ${preview.brightness!.name}'; + return builder.toPreview(); + }).toList(); + } +} + +@MultiBrightnessPreview(name: 'Primary Card') +Widget cardPreview() => const Card(child: Padding(padding: EdgeInsets.all(8.0), child: Text('Content'))); +``` diff --git a/skills/flutter-add-widget-test/SKILL.md b/skills/flutter-add-widget-test/SKILL.md new file mode 100644 index 0000000..01ac7ac --- /dev/null +++ b/skills/flutter-add-widget-test/SKILL.md @@ -0,0 +1,154 @@ +--- +name: flutter-add-widget-test +description: Implement a component-level test using `WidgetTester` to verify UI rendering and user interactions (tapping, scrolling, entering text). Use when validating that a specific widget displays correct data and responds to events as expected. +metadata: + model: models/gemini-3.1-pro-preview + last_modified: Tue, 21 Apr 2026 21:15:41 GMT +--- +# Writing Flutter Widget Tests + +## Contents +- [Setup & Configuration](#setup--configuration) +- [Core Components](#core-components) +- [Workflow: Implementing a Widget Test](#workflow-implementing-a-widget-test) +- [Interaction & State Management](#interaction--state-management) +- [Examples](#examples) + +## Setup & Configuration + +Ensure the testing environment is properly configured before authoring widget tests. + +1. Add the `flutter_test` dependency to the `dev_dependencies` section of `pubspec.yaml`. +2. Place all test files in the `test/` directory at the root of the project. +3. Suffix all test file names with `_test.dart` (e.g., `widget_test.dart`). + +## Core Components + +Utilize the following `flutter_test` components to interact with and validate the widget tree: + +* **`WidgetTester`**: The primary interface for building and interacting with widgets in the test environment. Provided automatically by the `testWidgets()` function. +* **`Finder`**: Locates widgets in the test environment (e.g., `find.text('Submit')`, `find.byType(TextField)`, `find.byKey(Key('submit_btn'))`). +* **`Matcher`**: Verifies the presence or state of widgets located by a `Finder` (e.g., `findsOneWidget`, `findsNothing`, `findsNWidgets(2)`, `matchesGoldenFile`). + +## Workflow: Implementing a Widget Test + +Copy the following checklist to track progress when implementing a new widget test. + +### Task Progress +- [ ] **Step 1: Define the test.** Use `testWidgets('description', (WidgetTester tester) async { ... })`. +- [ ] **Step 2: Build the widget.** Call `await tester.pumpWidget(MyWidget())` to render the UI. Wrap the widget in a `MaterialApp` or `Directionality` widget if it requires inherited directional or theme data. +- [ ] **Step 3: Locate elements.** Instantiate `Finder` objects for the target widgets. +- [ ] **Step 4: Verify initial state.** Use `expect(finder, matcher)` to validate the initial render. +- [ ] **Step 5: Simulate interactions.** Execute gestures or inputs (e.g., `await tester.tap(buttonFinder)`). +- [ ] **Step 6: Rebuild the tree.** Call `await tester.pump()` or `await tester.pumpAndSettle()` to process state changes. +- [ ] **Step 7: Verify updated state.** Use `expect()` to validate the UI after the interaction. +- [ ] **Step 8: Run and validate.** Execute `flutter test test/your_test_file_test.dart`. +- [ ] **Step 9: Feedback Loop.** Review test output -> identify failing matchers -> adjust widget logic or test assertions -> re-run until passing. + +## Interaction & State Management + +Apply the following conditional logic based on the type of interaction or state change being tested: + +* **If testing static rendering:** Call `await tester.pumpWidget()` once, then immediately run `expect()` assertions. +* **If testing standard state changes (e.g., button taps):** + 1. Call `await tester.tap(finder)`. + 2. Call `await tester.pump()` to trigger a single frame rebuild. +* **If testing animations, transitions, or asynchronous UI updates:** + 1. Trigger the action (e.g., `await tester.drag(finder, Offset(500, 0))`). + 2. Call `await tester.pumpAndSettle()` to repeatedly pump frames until no more frames are scheduled (animation completes). +* **If testing text input:** Call `await tester.enterText(textFieldFinder, 'Input string')`. +* **If testing items in a dynamic or long list:** Call `await tester.scrollUntilVisible(itemFinder, 500.0, scrollable: listFinder)` to ensure the target widget is rendered before interacting with it. + +## Examples + +### High-Fidelity Widget Test Implementation + +**Target Widget (`lib/todo_list.dart`):** +```dart +import 'package:flutter/material.dart'; + +class TodoList extends StatefulWidget { + const TodoList({super.key}); + + @override + State createState() => _TodoListState(); +} + +class _TodoListState extends State { + final todos = []; + final controller = TextEditingController(); + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: Column( + children: [ + TextField(controller: controller), + Expanded( + child: ListView.builder( + itemCount: todos.length, + itemBuilder: (context, index) { + final todo = todos[index]; + return Dismissible( + key: Key('$todo$index'), + onDismissed: (_) => setState(() => todos.removeAt(index)), + child: ListTile(title: Text(todo)), + ); + }, + ), + ), + ], + ), + floatingActionButton: FloatingActionButton( + onPressed: () { + setState(() { + todos.add(controller.text); + controller.clear(); + }); + }, + child: const Icon(Icons.add), + ), + ), + ); + } +} +``` + +**Test Implementation (`test/todo_list_test.dart`):** +```dart +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:my_app/todo_list.dart'; + +void main() { + testWidgets('Add and remove a todo item', (WidgetTester tester) async { + // 1. Build the widget + await tester.pumpWidget(const TodoList()); + + // 2. Verify initial state + expect(find.byType(ListTile), findsNothing); + + // 3. Enter text into the TextField + await tester.enterText(find.byType(TextField), 'Buy groceries'); + + // 4. Tap the add button + await tester.tap(find.byType(FloatingActionButton)); + + // 5. Rebuild the widget to reflect the new state + await tester.pump(); + + // 6. Verify the item was added + expect(find.text('Buy groceries'), findsOneWidget); + + // 7. Swipe the item to dismiss it + await tester.drag(find.byType(Dismissible), const Offset(500, 0)); + + // 8. Build the widget until the dismiss animation ends + await tester.pumpAndSettle(); + + // 9. Verify the item was removed + expect(find.text('Buy groceries'), findsNothing); + }); +} +``` 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-apply-architecture-best-practices/SKILL.md b/skills/flutter-apply-architecture-best-practices/SKILL.md new file mode 100644 index 0000000..791994b --- /dev/null +++ b/skills/flutter-apply-architecture-best-practices/SKILL.md @@ -0,0 +1,162 @@ +--- +name: flutter-apply-architecture-best-practices +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: Tue, 21 Apr 2026 20:11:20 GMT +--- +# Architecting Flutter Applications + +## Contents +- [Architectural Layers](#architectural-layers) +- [Project Structure](#project-structure) +- [Workflow: Implementing a New Feature](#workflow-implementing-a-new-feature) +- [Examples](#examples) + +## Architectural Layers + +Enforce strict Separation of Concerns by dividing the application into distinct layers. Never mix UI rendering with business logic or data fetching. + +### UI Layer (Presentation) +Implement the MVVM (Model-View-ViewModel) pattern to manage UI state and logic. +* **Views:** Write reusable, lean widgets. Restrict logic in Views to UI-specific operations (e.g., animations, layout constraints, simple routing). Pass all required data from the ViewModel. +* **ViewModels:** Manage UI state and handle user interactions. Extend `ChangeNotifier` (or use `Listenable`) to expose state. Expose immutable state snapshots to the View. Inject Repositories into ViewModels via the constructor. + +### Data Layer +Implement the Repository pattern to isolate data access logic and create a single source of truth. +* **Services:** Create stateless classes to wrap external APIs (HTTP clients, local databases, platform plugins). Return raw API models or `Result` wrappers. +* **Repositories:** Consume one or more Services. Transform raw API models into clean Domain Models. Handle caching, offline synchronization, and retry logic. Expose Domain Models to ViewModels. + +### Logic Layer (Domain - Optional) +* **Use Cases:** Implement this layer only if the application contains complex business logic that clutters the ViewModel, or if logic must be reused across multiple ViewModels. Extract this logic into dedicated Use Case (interactor) classes that sit between ViewModels and Repositories. + +## Project Structure + +Organize the codebase using a hybrid approach: group UI components by feature, and group Data/Domain components by type. + +```text +lib/ +├── data/ +│ ├── models/ # API models +│ ├── repositories/ # Repository implementations +│ └── services/ # API clients, local storage wrappers +├── domain/ +│ ├── models/ # Clean domain models +│ └── use_cases/ # Optional business logic classes +└── ui/ + ├── core/ # Shared widgets, themes, typography + └── features/ + └── [feature_name]/ + ├── view_models/ + └── views/ +``` + +## Workflow: Implementing a New Feature + +Follow this sequential workflow when adding a new feature to the application. Copy the checklist to track progress. + +### Task Progress +- [ ] **Step 1: Define Domain Models.** Create immutable data classes for the feature using `freezed` or `built_value`. +- [ ] **Step 2: Implement Services.** Create or update Service classes to handle external API communication. +- [ ] **Step 3: Implement Repositories.** Create the Repository to consume Services and return Domain Models. +- [ ] **Step 4: Apply Conditional Logic (Domain Layer).** + - *If the feature requires complex data transformation or cross-repository logic:* Create a Use Case class. + - *If the feature is a simple CRUD operation:* Skip to Step 5. +- [ ] **Step 5: Implement the ViewModel.** Create the ViewModel extending `ChangeNotifier`. Inject required Repositories/Use Cases. Expose immutable state and command methods. +- [ ] **Step 6: Implement the View.** Create the UI widget. Use `ListenableBuilder` or `AnimatedBuilder` to listen to ViewModel changes. +- [ ] **Step 7: Inject Dependencies.** Register the new Service, Repository, and ViewModel in the dependency injection container (e.g., `provider` or `get_it`). +- [ ] **Step 8: Run Validator.** Execute unit tests for the ViewModel and Repository. + - *Feedback Loop:* Run tests -> Review failures -> Fix logic -> Re-run until passing. + +## Examples + +### Data Layer: Service and Repository + +```dart +// 1. Service (Raw API interaction) +class ApiClient { + Future fetchUser(String id) async { + // HTTP GET implementation... + } +} + +// 2. Repository (Single source of truth, returns Domain Model) +class UserRepository { + UserRepository({required ApiClient apiClient}) : _apiClient = apiClient; + + final ApiClient _apiClient; + User? _cachedUser; + + Future getUser(String id) async { + if (_cachedUser != null) return _cachedUser!; + + final apiModel = await _apiClient.fetchUser(id); + _cachedUser = User(id: apiModel.id, name: apiModel.fullName); // Transform to Domain Model + return _cachedUser!; + } +} +``` + +### UI Layer: ViewModel and View + +```dart +// 3. ViewModel (State management and presentation logic) +class ProfileViewModel extends ChangeNotifier { + ProfileViewModel({required UserRepository userRepository}) + : _userRepository = userRepository; + + final UserRepository _userRepository; + + User? _user; + User? get user => _user; + + bool _isLoading = false; + bool get isLoading => _isLoading; + + Future loadProfile(String id) async { + _isLoading = true; + notifyListeners(); + + try { + _user = await _userRepository.getUser(id); + } finally { + _isLoading = false; + notifyListeners(); + } + } +} + +// 4. View (Dumb UI component) +class ProfileView extends StatelessWidget { + const ProfileView({super.key, required this.viewModel}); + + final ProfileViewModel viewModel; + + @override + Widget build(BuildContext context) { + return ListenableBuilder( + listenable: viewModel, + builder: (context, _) { + if (viewModel.isLoading) { + return const Center(child: CircularProgressIndicator()); + } + + final user = viewModel.user; + if (user == null) { + return const Center(child: Text('User not found')); + } + + return Column( + children: [ + Text(user.name), + ElevatedButton( + onPressed: () => viewModel.loadProfile(user.id), + child: const Text('Refresh'), + ), + ], + ); + }, + ); + } +} +``` 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-build-responsive-layout/SKILL.md b/skills/flutter-build-responsive-layout/SKILL.md new file mode 100644 index 0000000..b85bfd7 --- /dev/null +++ b/skills/flutter-build-responsive-layout/SKILL.md @@ -0,0 +1,139 @@ +--- +name: flutter-build-responsive-layout +description: Use `LayoutBuilder`, `MediaQuery`, or `Expanded/Flexible` to create a layout that adapts to different screen sizes. Use when you need the UI to look good on both mobile and tablet/desktop form factors. +metadata: + model: models/gemini-3.1-pro-preview + last_modified: Tue, 21 Apr 2026 20:17:40 GMT +--- +# Implementing Adaptive Layouts + +## Contents +- [Space Measurement Guidelines](#space-measurement-guidelines) +- [Widget Sizing and Constraints](#widget-sizing-and-constraints) +- [Device and Orientation Behaviors](#device-and-orientation-behaviors) +- [Workflow: Constructing an Adaptive Layout](#workflow-constructing-an-adaptive-layout) +- [Workflow: Optimizing for Large Screens](#workflow-optimizing-for-large-screens) +- [Examples](#examples) + +## Space Measurement Guidelines +Determine the available space accurately to ensure layouts adapt to the app window, not just the physical device. + +* **Use `MediaQuery.sizeOf(context)`** to get the size of the entire app window. +* **Use `LayoutBuilder`** to make layout decisions based on the parent widget's allocated space. Evaluate `constraints.maxWidth` to determine the appropriate widget tree to return. +* **Do not use `MediaQuery.orientationOf` or `OrientationBuilder`** near the top of the widget tree to switch layouts. Device orientation does not accurately reflect the available app window space. +* **Do not check for hardware types** (e.g., "phone" vs. "tablet"). Flutter apps run in resizable windows, multi-window modes, and picture-in-picture. Base all layout decisions strictly on available window space. + +## Widget Sizing and Constraints +Understand and apply Flutter's core layout rule: **Constraints go down. Sizes go up. Parent sets position.** + +* **Distribute Space:** Use `Expanded` and `Flexible` within `Row`, `Column`, or `Flex` widgets. + * Use `Expanded` to force a child to fill all remaining available space (equivalent to `Flexible` with `fit: FlexFit.tight` and a `flex` factor of 1.0). + * Use `Flexible` to allow a child to size itself up to a specific limit while still expanding/contracting. Use the `flex` factor to define the ratio of space consumption among siblings. +* **Constrain Width:** Prevent widgets from consuming all horizontal space on large screens. Wrap widgets like `GridView` or `ListView` in a `ConstrainedBox` or `Container` and define a `maxWidth` in the `BoxConstraints`. +* **Lazy Rendering:** Always use `ListView.builder` or `GridView.builder` when rendering lists with an unknown or large number of items. + +## Device and Orientation Behaviors +Ensure the app behaves correctly across all device form factors and input methods. + +* **Do not lock screen orientation.** Locking orientation causes severe layout issues on foldable devices, often resulting in letterboxing (the app centered with black borders). Android large format tiers require both portrait and landscape support. +* **Fallback for Locked Orientation:** If business requirements strictly mandate a locked orientation, use the `Display API` to retrieve physical screen dimensions instead of `MediaQuery`. `MediaQuery` fails to receive the larger window size in compatibility modes. +* **Support Multiple Inputs:** Implement support for basic mice, trackpads, and keyboard shortcuts. Ensure touch targets are appropriately sized and keyboard navigation is accessible. + +## Workflow: Constructing an Adaptive Layout + +Follow this workflow to implement a layout that adapts to the available `BoxConstraints`. + +**Task Progress:** +- [ ] Identify the target widget that requires adaptive behavior. +- [ ] Wrap the widget tree in a `LayoutBuilder`. +- [ ] Extract the `constraints.maxWidth` from the builder callback. +- [ ] Define an adaptive breakpoint (e.g., `largeScreenMinWidth = 600`). +- [ ] **If `maxWidth > largeScreenMinWidth`:** Return a large-screen layout (e.g., a `Row` placing a navigation sidebar and content area side-by-side). +- [ ] **If `maxWidth <= largeScreenMinWidth`:** Return a small-screen layout (e.g., a `Column` or standard navigation-style approach). +- [ ] Run validator -> resize the application window -> review layout transitions -> fix overflow errors. + +## Workflow: Optimizing for Large Screens + +Follow this workflow to prevent UI elements from stretching unnaturally on large displays. + +**Task Progress:** +- [ ] Identify full-width components (e.g., `ListView`, text blocks, forms). +- [ ] **If optimizing a list:** Convert `ListView.builder` to `GridView.builder` using `SliverGridDelegateWithMaxCrossAxisExtent` to automatically adjust column counts based on window size. +- [ ] **If optimizing a form or text block:** Wrap the component in a `ConstrainedBox`. +- [ ] Apply `BoxConstraints(maxWidth: [optimal_width])` to the `ConstrainedBox`. +- [ ] Wrap the `ConstrainedBox` in a `Center` widget to keep the constrained content centered on large screens. +- [ ] Run validator -> test on desktop/tablet target -> review horizontal stretching -> adjust `maxWidth` or grid extents. + +## Examples + +### Adaptive Layout using LayoutBuilder +Demonstrates switching between a mobile and desktop layout based on available width. + +```dart +import 'package:flutter/material.dart'; + +const double largeScreenMinWidth = 600.0; + +class AdaptiveLayout extends StatelessWidget { + const AdaptiveLayout({super.key}); + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + if (constraints.maxWidth > largeScreenMinWidth) { + return _buildLargeScreenLayout(); + } else { + return _buildSmallScreenLayout(); + } + }, + ); + } + + Widget _buildLargeScreenLayout() { + return Row( + children: [ + const SizedBox(width: 250, child: Placeholder(color: Colors.blue)), + const VerticalDivider(width: 1), + Expanded(child: const Placeholder(color: Colors.green)), + ], + ); + } + + Widget _buildSmallScreenLayout() { + return const Placeholder(color: Colors.green); + } +} +``` + +### Constraining Width on Large Screens +Demonstrates preventing a widget from consuming all horizontal space. + +```dart +import 'package:flutter/material.dart'; + +class ConstrainedContent extends StatelessWidget { + const ConstrainedContent({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 800.0, // Maximum width for readability + ), + child: ListView.builder( + itemCount: 50, + itemBuilder: (context, index) { + return ListTile( + title: Text('Item $index'), + ); + }, + ), + ), + ), + ); + } +} +``` 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-fix-layout-issues/SKILL.md b/skills/flutter-fix-layout-issues/SKILL.md new file mode 100644 index 0000000..3804a3c --- /dev/null +++ b/skills/flutter-fix-layout-issues/SKILL.md @@ -0,0 +1,130 @@ +--- +name: flutter-fix-layout-issues +description: Fixes Flutter layout errors (overflows, unbounded constraints) using Dart and Flutter MCP tools. Use when addressing "RenderFlex overflowed", "Vertical viewport was given unbounded height", or similar layout issues. +metadata: + model: models/gemini-3.1-pro-preview + last_modified: Tue, 21 Apr 2026 19:45:59 GMT +--- +# Resolving Flutter Layout Errors + +## Contents +- [Constraint Violation Diagnostics](#constraint-violation-diagnostics) +- [Layout Error Resolution Workflow](#layout-error-resolution-workflow) +- [Examples](#examples) + +## Constraint Violation Diagnostics + +Flutter layout operates on a strict rule: **Constraints go down. Sizes go up. Parent sets position.** Layout errors occur when this negotiation fails, typically due to unbounded constraints or unconstrained children. + +Diagnose layout failures using the following error signatures: + +* **"Vertical viewport was given unbounded height"**: Triggered when a scrollable widget (`ListView`, `GridView`) is placed inside an unconstrained vertical parent (`Column`). The parent provides infinite height, and the child attempts to expand infinitely. +* **"An InputDecorator...cannot have an unbounded width"**: Triggered when a `TextField` or `TextFormField` is placed inside an unconstrained horizontal parent (`Row`). The text field attempts to determine its width based on infinite available space. +* **"RenderFlex overflowed"**: Triggered when a child of a `Row` or `Column` requests a size larger than the parent's allocated constraints. Visually indicated by yellow and black warning stripes. +* **"Incorrect use of ParentData widget"**: Triggered when a `ParentDataWidget` is not a direct descendant of its required ancestor. (e.g., `Expanded` outside a `Flex`, `Positioned` outside a `Stack`). +* **"RenderBox was not laid out"**: A cascading side-effect error. Ignore this and look further up the stack trace for the primary constraint violation (usually an unbounded height/width error). + +## Layout Error Resolution Workflow + +Copy and use this checklist to systematically resolve layout constraint violations. + +### Task Progress +- [ ] Run the application in debug mode to capture the exact layout exception in the console. +- [ ] Identify the primary error message (ignore cascading "RenderBox was not laid out" errors). +- [ ] Apply the conditional fix based on the specific error type: + - **If "Vertical viewport was given unbounded height"**: Wrap the scrollable child (`ListView`, `GridView`) in an `Expanded` widget to consume remaining space, or wrap it in a `SizedBox` to provide an absolute height constraint. + - **If "An InputDecorator...cannot have an unbounded width"**: Wrap the `TextField` or `TextFormField` in an `Expanded` or `Flexible` widget. + - **If "RenderFlex overflowed"**: Constrain the overflowing child by wrapping it in an `Expanded` widget (to force it to fit) or a `Flexible` widget (to allow it to be smaller than the allocated space). + - **If "Incorrect use of ParentData widget"**: Move the `ParentDataWidget` to be a direct child of its required parent. Ensure `Expanded`/`Flexible` are direct children of `Row`/`Column`/`Flex`. Ensure `Positioned` is a direct child of `Stack`. +- [ ] Execute Flutter hot reload. +- [ ] Run validator -> review errors -> fix: Inspect the UI to verify the red/grey error screen or yellow/black overflow stripes are resolved. If new layout errors appear, repeat the workflow. + +## Examples + +### Fixing Unbounded Height (ListView in Column) + +**Input (Error State):** +```dart +// Throws "Vertical viewport was given unbounded height" +Column( + children: [ + const Text('Header'), + ListView( + children: const [ + ListTile(title: Text('Item 1')), + ListTile(title: Text('Item 2')), + ], + ), + ], +) +``` + +**Output (Resolved State):** +```dart +// Wrap ListView in Expanded to constrain its height to the remaining Column space +Column( + children: [ + const Text('Header'), + Expanded( + child: ListView( + children: const [ + ListTile(title: Text('Item 1')), + ListTile(title: Text('Item 2')), + ], + ), + ), + ], +) +``` + +### Fixing Unbounded Width (TextField in Row) + +**Input (Error State):** +```dart +// Throws "An InputDecorator...cannot have an unbounded width" +Row( + children: [ + const Icon(Icons.search), + TextField(), + ], +) +``` + +**Output (Resolved State):** +```dart +// Wrap TextField in Expanded to constrain its width to the remaining Row space +Row( + children: [ + const Icon(Icons.search), + Expanded( + child: TextField(), + ), + ], +) +``` + +### Fixing RenderFlex Overflow + +**Input (Error State):** +```dart +// Throws "A RenderFlex overflowed by X pixels on the right" +Row( + children: [ + const Icon(Icons.info), + const Text('This is a very long text string that will definitely overflow the available screen width and cause a RenderFlex error.'), + ], +) +``` + +**Output (Resolved State):** +```dart +// Wrap the Text widget in Expanded to force it to wrap within the available constraints +Row( + children: [ + const Icon(Icons.info), + Expanded( + child: const Text('This is a very long text string that will definitely overflow the available screen width and cause a RenderFlex error.'), + ), + ], +) +``` 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-implement-json-serialization/SKILL.md b/skills/flutter-implement-json-serialization/SKILL.md new file mode 100644 index 0000000..14009f4 --- /dev/null +++ b/skills/flutter-implement-json-serialization/SKILL.md @@ -0,0 +1,153 @@ +--- +name: flutter-implement-json-serialization +description: Create model classes with `fromJson` and `toJson` methods using `dart:convert`. Use when manually mapping JSON keys to class properties for simple data structures. +metadata: + model: models/gemini-3.1-pro-preview + last_modified: Tue, 21 Apr 2026 21:44:50 GMT +--- +# Serializing JSON Manually in Flutter + +## Contents +- [Core Guidelines](#core-guidelines) +- [Workflow: Implementing a Serializable Model](#workflow-implementing-a-serializable-model) +- [Workflow: Fetching and Parsing JSON](#workflow-fetching-and-parsing-json) +- [Examples](#examples) + +## Core Guidelines + +- **Import `dart:convert`**: Utilize Flutter's built-in `dart:convert` library for manual JSON encoding (`jsonEncode`) and decoding (`jsonDecode`). +- **Enforce Type Safety**: Always cast the `dynamic` result of `jsonDecode()` to the expected type, typically `Map` for objects or `List` for arrays. +- **Encapsulate Serialization Logic**: Define plain model classes containing properties corresponding to the JSON structure. Implement a `fromJson` factory constructor and a `toJson` method within the model. +- **Handle Background Parsing**: If parsing large JSON documents (execution time > 16ms), offload the parsing logic to a separate isolate using Flutter's `compute()` function to prevent UI jank. +- **Throw Exceptions on Failure**: When handling HTTP responses, throw an exception if the status code is not successful (e.g., not 200 OK or 201 Created). Do not return `null`. + +## Workflow: Implementing a Serializable Model + +Use this checklist to implement manual JSON serialization for a data model. + +**Task Progress:** +- [ ] Define the plain model class with `final` properties. +- [ ] Implement the `factory Model.fromJson(Map json)` constructor. +- [ ] Implement the `Map toJson()` method. +- [ ] Write unit tests for both serialization methods. +- [ ] Run validator -> review type mismatch errors -> fix casting logic. + +1. **Define the Model**: Create a class with properties matching the JSON keys. +2. **Implement `fromJson`**: Extract values from the `Map` and cast them to the appropriate Dart types. Use pattern matching or explicit casting. +3. **Implement `toJson`**: Return a `Map` mapping the class properties back to their JSON string keys. +4. **Validate**: Execute unit tests to ensure type safety, autocompletion, and compile-time exception handling function correctly. + +## Workflow: Fetching and Parsing JSON + +Use this conditional workflow when retrieving and parsing JSON from a network request. + +**Task Progress:** +- [ ] Execute the HTTP request. +- [ ] Validate the response status code. +- [ ] Determine parsing strategy (Synchronous vs. Isolate). +- [ ] Decode and map the JSON to the model. + +1. **Execute Request**: Use the `http` package to perform the network call. +2. **Validate Response**: + - If `response.statusCode == 200` (or 201 for POST), proceed to parsing. + - If the status code indicates failure, throw an `Exception`. +3. **Determine Parsing Strategy**: + - If parsing a **small payload** (e.g., a single object), parse synchronously on the main thread. + - If parsing a **large payload** (e.g., an array of thousands of objects), use `compute(parseFunction, response.body)` to parse in a background isolate. +4. **Decode and Map**: Pass the decoded JSON to your model's `fromJson` constructor. + +## Examples + +### High-Fidelity Model Implementation + +```dart +import 'dart:convert'; + +class User { + final int id; + final String name; + final String email; + + const User({ + required this.id, + required this.name, + required this.email, + }); + + // Factory constructor for deserialization + factory User.fromJson(Map json) { + return switch (json) { + { + 'id': int id, + 'name': String name, + 'email': String email, + } => + User( + id: id, + name: name, + email: email, + ), + _ => throw const FormatException('Failed to load User.'), + }; + } + + // Method for serialization + Map toJson() { + return { + 'id': id, + 'name': name, + 'email': email, + }; + } +} +``` + +### Synchronous Parsing (Small Payload) + +```dart +import 'dart:convert'; +import 'package:http/http.dart' as http; + +Future fetchUser(http.Client client, int userId) async { + final response = await client.get( + Uri.parse('https://api.example.com/users/$userId'), + headers: {'Accept': 'application/json'}, + ); + + if (response.statusCode == 200) { + // Decode returns dynamic, cast to Map + final Map jsonMap = jsonDecode(response.body) as Map; + return User.fromJson(jsonMap); + } else { + throw Exception('Failed to load user'); + } +} +``` + +### Background Parsing (Large Payload) + +```dart +import 'dart:convert'; +import 'package:flutter/foundation.dart'; +import 'package:http/http.dart' as http; + +// Top-level function required for compute() +List parseUsers(String responseBody) { + final parsed = (jsonDecode(responseBody) as List).cast>(); + return parsed.map((json) => User.fromJson(json)).toList(); +} + +Future> fetchUsers(http.Client client) async { + final response = await client.get( + Uri.parse('https://api.example.com/users'), + headers: {'Accept': 'application/json'}, + ); + + if (response.statusCode == 200) { + // Offload expensive parsing to a background isolate + return compute(parseUsers, response.body); + } else { + throw Exception('Failed to load users'); + } +} +``` 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 `