From 8c8e846dab3c3d12cce55e43fc48370569958955 Mon Sep 17 00:00:00 2001 From: Sarbagya Dhaubanjar Date: Tue, 13 Jan 2026 15:38:09 +0545 Subject: [PATCH 1/3] addressed melos breaking changes --- .github/workflows/gen-cov.yml | 11 ++-- devtools_options.yaml | 3 + .../clean-framework/architecture-overview.mdx | 29 +++++----- docs/codelabs/clean-framework/intro.mdx | 17 ++++-- melos.yaml | 44 -------------- packages/clean_framework/CHANGELOG.md | 2 +- packages/clean_framework/example/pubspec.yaml | 4 +- packages/clean_framework/pubspec.yaml | 6 +- .../theme_example/pubspec.yaml | 4 +- .../clean_framework_firestore/pubspec.yaml | 6 +- .../graphql_example/pubspec.yaml | 6 +- packages/clean_framework_graphql/pubspec.yaml | 6 +- .../clean_framework_http/example/pubspec.yaml | 4 +- packages/clean_framework_http/pubspec.yaml | 6 +- packages/clean_framework_router/pubspec.yaml | 6 +- packages/clean_framework_test/pubspec.yaml | 6 +- pubspec.yaml | 58 ++++++++++++++++++- 17 files changed, 129 insertions(+), 89 deletions(-) create mode 100644 devtools_options.yaml delete mode 100644 melos.yaml diff --git a/.github/workflows/gen-cov.yml b/.github/workflows/gen-cov.yml index 22ce453e..453fb5de 100644 --- a/.github/workflows/gen-cov.yml +++ b/.github/workflows/gen-cov.yml @@ -3,7 +3,7 @@ name: Quality Check on: push: pull_request: - types: [ opened, reopened ] + types: [opened, reopened] jobs: gen-cov: @@ -17,10 +17,10 @@ jobs: - name: Setup Flutter Stable uses: subosito/flutter-action@v2 with: - channel: 'stable' + channel: "stable" cache: true - - name: 'Install Tools' + - name: "Install Tools" run: | ./.github/workflows/scripts/install-tools.sh @@ -37,9 +37,8 @@ jobs: uses: codecov/codecov-action@v3 with: token: ${{secrets.CODECOV_TOKEN}} - files: ./packages/clean_framework/coverage/lcov.info,./packages/clean_framework_firestore/coverage/lcov.info,./packages/clean_framework_graphql/coverage/lcov.info,./packages/clean_framework_rest/coverage/lcov.info,./packages/clean_framework_router/coverage/lcov.info,./packages/clean_framework_test/coverage/lcov.info + files: ./packages/clean_framework/coverage/lcov.info,./packages/clean_framework_firestore/coverage/lcov.info,./packages/clean_framework_graphql/coverage/lcov.info,./packages/clean_framework_http/coverage/lcov.info,./packages/clean_framework_router/coverage/lcov.info,./packages/clean_framework_test/coverage/lcov.info name: clean-framework-coverage - # pana: # defaults: # run: @@ -59,4 +58,4 @@ jobs: # # - name: Verify Pub Score # run: | -# ./.github/workflows/scripts/verify-pub-score.sh \ No newline at end of file +# ./.github/workflows/scripts/verify-pub-score.sh diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 00000000..fa0b357c --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/docs/codelabs/clean-framework/architecture-overview.mdx b/docs/codelabs/clean-framework/architecture-overview.mdx index 4f86692e..48cdcdb7 100644 --- a/docs/codelabs/clean-framework/architecture-overview.mdx +++ b/docs/codelabs/clean-framework/architecture-overview.mdx @@ -9,19 +9,19 @@ Clean Architecture is a unique, robust blueprint for developing software, outlin ### Why Clean Architecture Stands Out 1. **Separation of Concerns:** -The core philosophy of Clean Architecture is the separation of software into distinct layers, each with a clearly defined role. This separation makes the system more flexible, maintainable, and testable, and differs from other patterns such as MVC, where there is less separation between the core business logic and UI, for example. + The core philosophy of Clean Architecture is the separation of software into distinct layers, each with a clearly defined role. This separation makes the system more flexible, maintainable, and testable, and differs from other patterns such as MVC, where there is less separation between the core business logic and UI, for example. 2. **The Dependency Rule:** -This rule states that dependencies should only point inwards: the innermost layers should not depend on the outer layers. That way, the business logic and application rules will not be affected by changes to external elements like the UI or database systems. As you'll find, this rule is what informs the structure of the rest of the framework, and is the key to avoiding messy, hair-pulling bad dependency cycles. + This rule states that dependencies should only point inwards: the innermost layers should not depend on the outer layers. That way, the business logic and application rules will not be affected by changes to external elements like the UI or database systems. As you'll find, this rule is what informs the structure of the rest of the framework, and is the key to avoiding messy, hair-pulling bad dependency cycles. These are the two core features of Clean Architecture that we believe makes it unique among the available development patterns. There are many similarities between Clean Architecture and other patterns as well as many differences, and we'll cover some of those in another article. We'll also cover the different layers involved with Clean Architecture shortly, and how we went about implementing each in Clean Framework. ## Philosophy to Implementation -There are many potential benefits to following a predefined development pattern such as Clean Architecture. However, rendering high-level development philosophies into concrete implementations is not a simple task, and much of it remains up to individual interpretation. Many questions may need to be answered in order to begin writing your app, such as: +There are many potential benefits to following a predefined development pattern such as Clean Architecture. However, rendering high-level development philosophies into concrete implementations is not a simple task, and much of it remains up to individual interpretation. Many questions may need to be answered in order to begin writing your app, such as: - What will our Use Cases look like? -- How will we manage dependencies to adhere to the dependency rule? +- How will we manage dependencies to adhere to the dependency rule? - What parts of the architecture do we want to deviate from, and which do we want to adhere closely to? It can be somewhat time consuming to reach a consensus on what following a pattern such as Clean Architecture might look like for your app. In the case of Clean Architecture, Clean Framework is our answer to this problem in Flutter. @@ -31,20 +31,23 @@ It can be somewhat time consuming to reach a consensus on what following a patte Clean Framework is a toolkit of classes and implementations that aid developers in creating a layered architecture for Flutter apps, following the principles of Clean Architecture. It provides a structured implementation of the pattern with a focus on maintainability, testability, and separation of concerns, while still preserving flexibility, quick iteration and handling all of the complexities of inter-layer data flow for you. That way, you can spend more time fleshing out the business logic and user experience of your app, and less time on architectural concerns. When developing Clean Framework, we've aimed to utilize the best that Clean Architecture has to offer while simplifying some areas we feel are too complex. Clean Framework consists of a few different layers, based closely on those laid out by Clean Architecture: + 1. **Entity Layer**: Data-wise, this is heart of your app. It consists of objects called `Entities` that contain the core state of each feature. These Entities are immutable, only containing data fields and methods that aid in data retrieval and integration. This layer should reside within the `domain` directory of each feature. 2. **Use Case Layer**: Here's where most of the business logic lives. Use Cases manage the data in Entities and direct data flow. Being Plain Old Dart Objects (PODOs), Use Cases are completely agnostic to the implementation of outside layers, and can interact with various object types via Data Transfer Objects (DTOs) called `DomainInput`s and `DomainModel`s without fussing over details. This layer should reside within the `domain` directory of each feature, along with the Entity layer. - - `DomainModel`s are essentially a snapshot of a part of, or the whole state (Entity) of the feature at a point in time. They transfer data from the Use Case layer to other layers. - - `DomainInput`s contain data originating in external services or layers that needs to reach the Use Case. + + - `DomainModel`s are essentially a snapshot of a part of, or the whole state (Entity) of the feature at a point in time. They transfer data from the Use Case layer to other layers. + - `DomainInput`s contain data originating in external services or layers that needs to reach the Use Case. 3. **Adapters Layer**: This layer translates `DomainInputs` and `DomainModels` into specific messages. It includes two main components: - - **Presenters**: The Presenter class translates data from DomainModels originating in the Use Case layer into `ViewModels`, which contain data and behaviors (such as callbacks) destined for the UI. They handle the UI logic that isn't business-related, such as navigation. Presenters subscribe to DomainModels from Use Cases and update the UI with new View Models. Presenters should reside within the `presentation` directory of each feature, as their primary role is the management of the UI. - - **Gateways**: These handle external data requests to sources such as REST servers, databases, hardware, etc. translating DomainModels into actionable requests. There are two types: a regular `Gateway` for immediate (synchronous) responses and a `WatcherGateway` for ongoing subscriptions. These objects should reside in the `external_interface` directory of each feature, as they are the bridge between the external interface layer and the use case layer. + + - **Presenters**: The Presenter class translates data from DomainModels originating in the Use Case layer into `ViewModels`, which contain data and behaviors (such as callbacks) destined for the UI. They handle the UI logic that isn't business-related, such as navigation. Presenters subscribe to DomainModels from Use Cases and update the UI with new View Models. Presenters should reside within the `presentation` directory of each feature, as their primary role is the management of the UI. + - **Gateways**: These handle external data requests to sources such as REST servers, databases, hardware, etc. translating DomainModels into actionable requests. There are two types: a regular `Gateway` for immediate (synchronous) responses and a `WatcherGateway` for ongoing subscriptions. These objects should reside in the `external_interface` directory of each feature, as they are the bridge between the external interface layer and the use case layer. 4. **External Interfaces Layer**: This layer is where your features interact with external libraries and dependencies. External Interfaces wait for requests to be received from feature Use Cases, which are then processed depending on request type. Clean Framework offers some ready-to-use sub-packages for `GraphQL`, `REST`, and `Cloud FireStore`: - - [clean_framework_graphql](https://pub.dev/packages/clean_framework_graphql) - - [clean_framework_rest](https://pub.dev/packages/clean_framework_rest) - - [clean_framework_firestore](https://pub.dev/packages/clean_framework_firestore) + - [clean_framework_graphql](https://pub.dev/packages/clean_framework_graphql) + - [clean_framework_http](https://pub.dev/packages/clean_framework_http) + - [clean_framework_firestore](https://pub.dev/packages/clean_framework_firestore) As hinted at while explaining the different layers, Clean Framework intends for each app to be organized by feature. We'll go over the project structure of a typical Clean Framework app in the next article. @@ -54,7 +57,7 @@ We provide a few different example projects to showcase the usage and different #### [example_rest](https://github.com/AcmeSoftwareLLC/clean_framework_examples/tree/main/example_rest): Pokémon App w/RESTful Backend - + Simple Pokémon browser app, showcasing how a simple app with a clean UI and a RESTful backend (PokéAPI REST API v2) might be written using `clean_framework.` @@ -72,4 +75,4 @@ Sample app with a bare-bones UI that showcases how one might reach beyond the co ## Conclusion -By emphasizing separation of concerns, dependency inversion, and organizing code around features rather than technical concerns, Clean Architecture facilitates the creation of applications that are easier to test, extend, and maintain. Clean Framework aims to aid developers in adhering to these principles, providing a toolkit that marries theory with practice. Stay tuned to start diving into the process of developing a simple Clean Framework app! \ No newline at end of file +By emphasizing separation of concerns, dependency inversion, and organizing code around features rather than technical concerns, Clean Architecture facilitates the creation of applications that are easier to test, extend, and maintain. Clean Framework aims to aid developers in adhering to these principles, providing a toolkit that marries theory with practice. Stay tuned to start diving into the process of developing a simple Clean Framework app! diff --git a/docs/codelabs/clean-framework/intro.mdx b/docs/codelabs/clean-framework/intro.mdx index 92442f4f..c34b9312 100644 --- a/docs/codelabs/clean-framework/intro.mdx +++ b/docs/codelabs/clean-framework/intro.mdx @@ -1,11 +1,15 @@ # Overview + Clean Framework is a toolkit of classes and implementations that aid developers in creating a layered architecture for any Flutter app, following the principles of Clean Architecture by Uncle Bob (Robert C. Martin). **Understanding the Layers** Clean Architecture is all about separating code into layers to avoid interdependencies and to separate concerns. The following diagram outlines how Clean Architecture proposes implementing different layers. - + The concept of layering an architecture to distinguish domain logic from implementation specifics isn't new, with various methodologies being proposed in the past, such as Hexagonal Architecture. In developing Clean Architecture, Robert Martin has effectively integrated key elements from these existing frameworks, so you may be familiar with some of the terminology. @@ -16,12 +20,13 @@ Here's a quick overview of the different Clean Framework layers: 2. **Use Case Layer**: Here's where most of the business logic lives. Use Cases manage the data in Entities and direct data flow. They handle two classes: DomainInput and DomainModel which, similarly to Domain-Driven Design events, move data into or out of the Use Case (respectively). Other layers can only use these components to send and receive data to and from Entities. Being Plain Old Dart Objects (PODOs), Use Cases are completely agnostic to the implementation of outside layers, and can interact with various object types without fussing over details. Use Cases interact with DomainInputs and DomainModels through `requests` and `transformers`, which can be either synchronous or subscriptions. 3. **Adapters Layer**: This layer translates DomainInputs and DomainModels into specific messages. It includes two main components: - - **Presenters**: Presenters turn DomainModels from the Use Case into ViewModels, containing data and behaviors (like callbacks). They handle the UI logic that's not business-related, such as navigation. Presenters subscribe to DomainModels from Use Cases and update the UI with new View Models. - - **Gateways**: These handle external data requests to sources such as REST servers, databases, hardware, etc. translating DomainModels into actionable requests. There are two types: a regular `Gateway` for immediate (synchronous) responses and a `WatcherGateway` for ongoing subscriptions. + + - **Presenters**: Presenters turn DomainModels from the Use Case into ViewModels, containing data and behaviors (like callbacks). They handle the UI logic that's not business-related, such as navigation. Presenters subscribe to DomainModels from Use Cases and update the UI with new View Models. + - **Gateways**: These handle external data requests to sources such as REST servers, databases, hardware, etc. translating DomainModels into actionable requests. There are two types: a regular `Gateway` for immediate (synchronous) responses and a `WatcherGateway` for ongoing subscriptions. 4. **External Interfaces Layer**: This layer is where your features interact with external libraries and dependencies. External Interfaces wait for requests to be received from feature Use Cases, which are then processed depending on request type. Clean Framework offers some ready-to-use sub-packages for `GraphQL`, `REST`, and `Cloud FireStore`: - - [clean_framework_graphql](https://pub.dev/packages/clean_framework_graphql) - - [clean_framework_rest](https://pub.dev/packages/clean_framework_rest) - - [clean_framework_firestore](https://pub.dev/packages/clean_framework_firestore) + - [clean_framework_graphql](https://pub.dev/packages/clean_framework_graphql) + - [clean_framework_http](https://pub.dev/packages/clean_framework_http) + - [clean_framework_firestore](https://pub.dev/packages/clean_framework_firestore) As a side note, the UI layer is considered a type of External Interface layer, as it communicates through an adapter (the Presenter) to exchange state changes with the entities. In the next section, we'll cover the typical structure of a Clean Framework project. diff --git a/melos.yaml b/melos.yaml deleted file mode 100644 index 731432e0..00000000 --- a/melos.yaml +++ /dev/null @@ -1,44 +0,0 @@ -name: clean_framework - -packages: - - example - - packages/** - -command: - bootstrap: - usePubspecOverrides: true - -scripts: - analyze: - # We are setting the concurrency to 1 because a higher concurrency can crash - # the analysis server on low performance machines (like GitHub Actions). - run: | - melos exec -c 1 -- \ - dart analyze . --fatal-infos - description: | - Run `dart analyze` in all packages. - - Note: you can also rely on your IDEs Dart Analysis / Issues window. - - test: - run: | - melos exec -c 6 --fail-fast -- \ - "flutter test --no-pub" - description: Run `flutter test` for a specific package. - select-package: - dir-exists: - - test - - coverage: - run: | - melos exec -c 6 --fail-fast -- \ - "flutter test --no-pub --coverage" - description: Run `flutter test --coverage` for a specific package. - select-package: - dir-exists: - - test - - coverage:all: - run: | - melos run coverage --no-select - description: | - Run all tests available and generate coverage. \ No newline at end of file diff --git a/packages/clean_framework/CHANGELOG.md b/packages/clean_framework/CHANGELOG.md index 061b4f4e..853972b3 100644 --- a/packages/clean_framework/CHANGELOG.md +++ b/packages/clean_framework/CHANGELOG.md @@ -165,7 +165,7 @@ For entity updates that do not need to rebuild the UI, should be wrapped inside Sub-packages: - clean_framework_router - clean_framework_graphql -- clean_framework_rest +- clean_framework_http - clean_framework_firestore - clean_framework_test ``` diff --git a/packages/clean_framework/example/pubspec.yaml b/packages/clean_framework/example/pubspec.yaml index f0556fcd..8bf3511c 100644 --- a/packages/clean_framework/example/pubspec.yaml +++ b/packages/clean_framework/example/pubspec.yaml @@ -4,7 +4,9 @@ publish_to: none version: 2.0.3+3 environment: - sdk: '>=3.2.0 <4.0.0' + sdk: '>=3.8.0 <4.0.0' + +resolution: workspace dependencies: animations: ^2.0.11 diff --git a/packages/clean_framework/pubspec.yaml b/packages/clean_framework/pubspec.yaml index efbe4721..15f9cbad 100644 --- a/packages/clean_framework/pubspec.yaml +++ b/packages/clean_framework/pubspec.yaml @@ -13,8 +13,10 @@ topics: - feature-flag environment: - sdk: '>=3.0.0 <4.0.0' - flutter: '>=3.0.0' + sdk: '>=3.8.0 <4.0.0' + flutter: '>=3.32.0' + +resolution: workspace dependencies: equatable: ^2.0.5 diff --git a/packages/clean_framework/theme_example/pubspec.yaml b/packages/clean_framework/theme_example/pubspec.yaml index 442579ab..9d8eb136 100644 --- a/packages/clean_framework/theme_example/pubspec.yaml +++ b/packages/clean_framework/theme_example/pubspec.yaml @@ -19,7 +19,9 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: '>=3.2.5 <4.0.0' + sdk: '>=3.8.0 <4.0.0' + +resolution: workspace # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions diff --git a/packages/clean_framework_firestore/pubspec.yaml b/packages/clean_framework_firestore/pubspec.yaml index 9ba3eb8d..95eeb533 100644 --- a/packages/clean_framework_firestore/pubspec.yaml +++ b/packages/clean_framework_firestore/pubspec.yaml @@ -5,8 +5,10 @@ homepage: https://acmesoftware.com/ repository: https://github.com/AcmeSoftwareLLC/clean_framework/packages/clean_framework_firestore environment: - sdk: '>=3.0.0 <4.0.0' - flutter: '>=3.0.0' + sdk: '>=3.8.0 <4.0.0' + flutter: '>=3.32.0' + +resolution: workspace dependencies: clean_framework: ^4.1.3 diff --git a/packages/clean_framework_graphql/graphql_example/pubspec.yaml b/packages/clean_framework_graphql/graphql_example/pubspec.yaml index cdcfc10a..6ac969d0 100644 --- a/packages/clean_framework_graphql/graphql_example/pubspec.yaml +++ b/packages/clean_framework_graphql/graphql_example/pubspec.yaml @@ -19,7 +19,9 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: '>=3.2.5 <4.0.0' + sdk: '>=3.8.0 <4.0.0' + +resolution: workspace # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -48,7 +50,7 @@ dev_dependencies: # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. - flutter_lints: ^2.0.0 + flutter_lints: ^6.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/packages/clean_framework_graphql/pubspec.yaml b/packages/clean_framework_graphql/pubspec.yaml index 0d6fa57f..c43e6544 100644 --- a/packages/clean_framework_graphql/pubspec.yaml +++ b/packages/clean_framework_graphql/pubspec.yaml @@ -5,8 +5,10 @@ homepage: https://acmesoftware.com/ repository: https://github.com/AcmeSoftwareLLC/clean_framework/packages/clean_framework_graphql environment: - sdk: '>=2.17.0 <4.0.0' - flutter: '>=3.0.0' + sdk: '>=3.8.0 <4.0.0' + flutter: '>=3.32.0' + +resolution: workspace dependencies: clean_framework: ^4.1.3 diff --git a/packages/clean_framework_http/example/pubspec.yaml b/packages/clean_framework_http/example/pubspec.yaml index a576803c..eeec52b8 100644 --- a/packages/clean_framework_http/example/pubspec.yaml +++ b/packages/clean_framework_http/example/pubspec.yaml @@ -5,7 +5,9 @@ publish_to: none version: 1.0.0+1 environment: - sdk: '>=3.2.0 <4.0.0' + sdk: '>=3.8.0 <4.0.0' + +resolution: workspace dependencies: clean_framework: ^4.1.1 diff --git a/packages/clean_framework_http/pubspec.yaml b/packages/clean_framework_http/pubspec.yaml index c59c10da..8e5d5fe2 100644 --- a/packages/clean_framework_http/pubspec.yaml +++ b/packages/clean_framework_http/pubspec.yaml @@ -5,8 +5,10 @@ homepage: https://acmesoftware.com repository: https://github.com/AcmeSoftwareLLC/clean_framework/tree/main/packages/clean_framework_http environment: - sdk: '>=3.0.0 <4.0.0' - flutter: '>=3.0.0' + sdk: '>=3.8.0 <4.0.0' + flutter: '>=3.32.0' + +resolution: workspace dependencies: clean_framework: ^4.1.3 diff --git a/packages/clean_framework_router/pubspec.yaml b/packages/clean_framework_router/pubspec.yaml index 4b509a6a..fcfdd597 100644 --- a/packages/clean_framework_router/pubspec.yaml +++ b/packages/clean_framework_router/pubspec.yaml @@ -5,8 +5,10 @@ homepage: https://acmesoftware.com/ repository: https://github.com/AcmeSoftwareLLC/clean_framework/tree/main/packages/clean_framework_router environment: - sdk: '>=3.0.0 <4.0.0' - flutter: '>=3.0.0' + sdk: '>=3.8.0 <4.0.0' + flutter: '>=3.32.0' + +resolution: workspace dependencies: clean_framework: ^4.1.3 diff --git a/packages/clean_framework_test/pubspec.yaml b/packages/clean_framework_test/pubspec.yaml index 43faa7e1..498abd6a 100644 --- a/packages/clean_framework_test/pubspec.yaml +++ b/packages/clean_framework_test/pubspec.yaml @@ -5,8 +5,10 @@ homepage: https://acmesoftware.com/ repository: https://github.com/AcmeSoftwareLLC/clean_framework/packages/clean_framework_test environment: - sdk: '>=3.0.0 <4.0.0' - flutter: '>=3.0.0' + sdk: '>=3.8.0 <4.0.0' + flutter: '>=3.32.0' + +resolution: workspace dependencies: clean_framework: ^4.1.3 diff --git a/pubspec.yaml b/pubspec.yaml index 0be0108a..380b17e7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,8 +1,62 @@ name: clean_framework_workspace - +publish_to: none environment: - sdk: '>=3.4.1 <4.0.0' + sdk: '>=3.8.0 <4.0.0' + +workspace: + - packages/clean_framework + - packages/clean_framework/example + - packages/clean_framework/theme_example + - packages/clean_framework_router + - packages/clean_framework_http + - packages/clean_framework_http/example + - packages/clean_framework_firestore + - packages/clean_framework_graphql + - packages/clean_framework_graphql/graphql_example + - packages/clean_framework_test dev_dependencies: melos: ^7.3.0 very_good_analysis: ^10.0.0 + +melos: + command: + bootstrap: + environment: + sdk: '>=3.8.0 <4.0.0' + flutter: '>=3.32.0' + + scripts: + analyze: + # We are setting the concurrency to 1 because a higher concurrency can crash + # the analysis server on low performance machines (like GitHub Actions). + run: | + melos exec -c 1 -- \ + dart analyze . --fatal-infos + description: | + Run `dart analyze` in all packages. + - Note: you can also rely on your IDEs Dart Analysis / Issues window. + + test: + run: | + melos exec -c 6 --fail-fast -- \ + "flutter test --no-pub" + description: Run `flutter test` for a specific package. + select-package: + dir-exists: + - test + + coverage: + run: | + melos exec -c 6 --fail-fast -- \ + "flutter test --no-pub --coverage" + description: Run `flutter test --coverage` for a specific package. + select-package: + dir-exists: + - test + + coverage:all: + run: | + melos run coverage --no-select + description: | + Run all tests available and generate coverage. From a6b2d0ab4c6bb534d8fd420d28772a6677f3c990 Mon Sep 17 00:00:00 2001 From: Sarbagya Dhaubanjar Date: Tue, 13 Jan 2026 16:02:45 +0545 Subject: [PATCH 2/3] made analyzer happy --- .../clean-framework/bonus-theme-example.mdx | 25 ++++- .../example/lib/routing/poke_router.dart | 4 +- .../pokemon_species_gateway_test.dart | 10 +- .../example/test/flow_test.dart | 2 + .../src/presentation/presenter/presenter.dart | 52 ++++++---- .../lib/src/utilities/either.dart | 3 +- .../test/providers/providers_test.dart | 30 +++--- .../features/home/presentation/home_ui.dart | 49 ++++------ .../theme_example/lib/router.dart | 7 +- .../lib/src/firebase_client_fake.dart | 2 +- .../firebase_external_interface_test.dart | 52 ++++++---- .../graphql_example/lib/router.dart | 7 +- .../lib/src/graphql_logger.dart | 4 +- .../lib/src/graphql_service.dart | 27 +++--- .../test/graphql_external_interface_test.dart | 8 +- .../clean_framework_http_example_router.dart | 2 +- .../lib/src/app_route.dart | 50 +++++----- .../test/app_router_test.dart | 97 ++++++++++--------- .../lib/src/presenter_test.dart | 15 +-- 19 files changed, 248 insertions(+), 198 deletions(-) diff --git a/docs/codelabs/clean-framework/bonus-theme-example.mdx b/docs/codelabs/clean-framework/bonus-theme-example.mdx index 0a8e291d..17b42e6f 100644 --- a/docs/codelabs/clean-framework/bonus-theme-example.mdx +++ b/docs/codelabs/clean-framework/bonus-theme-example.mdx @@ -7,6 +7,7 @@ At this point, we've covered the domain layer, features, and setting up the UI l For this guide, we won't be making any changes to the Pokemon app we've been writing, but instead outlining a separate example feature as a proof of concept. You can find the source code for this section in the `theme_example` project that is part of `clean_framework`. Here's what we'll be setting up for this guide: + 1. A screen that allows the user to choose the app theme (called `HomeUI`), 2. Another UI-derived class, within the same feature, that converts the user's theme choice to a flutter `ThemeMode` object (called `ExampleThemeModeWrapper`), 3. A `ThemeExampleApp` top-level widget that will utilize the `ExampleThemeModeWrapper` UI to provide the themeMode choice to `MaterialApp.router`. @@ -15,7 +16,7 @@ Our feature will be called `Home`, and our sub-feature (that contains `ExampleTh #### Setting Things Up -Since we've already covered the domain and UI layers for Clean Framework apps in previous sections, we won't go into depth about how they work again here. +Since we've already covered the domain and UI layers for Clean Framework apps in previous sections, we won't go into depth about how they work again here. Our theme example app will contain a single feature, laid out as follows: @@ -41,6 +42,7 @@ features First, we need to get our data containers ready. Let's start with writing the `HomeEntity` for our `home` feature: #### File: `lib/features/home/domain/home_entity.dart` + ```dart import 'package:clean_framework/clean_framework.dart'; @@ -85,6 +87,7 @@ Our entity only has a single field: `appTheme`. The `AppTheme` type will be our Next, we are going to need two `DomainModel`s, since we're going to end up with two `UI` classes: `HomeDomainToUIModel`, and `HomeThemeDomainToUIModel`: #### File: `lib/features/home/domain/home_domain_models.dart` + ```dart import 'package:clean_framework/clean_framework.dart'; import 'package:theme_example/features/home/domain/home_entity.dart'; @@ -121,6 +124,7 @@ Both of our `UI` classes will need the selected `AppTheme`, so we have an `appTh Finally, now that we have our domain layer data objects, we just need to set up the `UseCase`: #### File: `lib/features/home/domain/home_use_case.dart` + ```dart import 'package:clean_framework/clean_framework.dart'; import 'package:theme_example/features/home/domain/home_domain_models.dart'; @@ -147,12 +151,14 @@ class HomeUseCase extends UseCase { ``` We're going to have two functions in our `UseCase`: + 1. `getTheme()`: This is going to be called on initialization (`onLayoutReady()`) from our main `Presenter`. 2. `updateTheme(AppTheme?)`: This function is what will update the entity each time the user makes a new theme selection from our main `UI`. Lastly, we're going to need two `DomainModelTransformer`s that each of our Presenters can subscribe to. We usually put these just below the `UseCase` in the same file. #### File: `lib/features/home/domain/home_use_case.dart` + ```dart class HomeDomainToUIModelTransformer extends DomainModelTransformer { @@ -174,11 +180,13 @@ class HomeThemeDomainToUIModelTransformer } } ``` + Just like our two `DomainModel`s, our `DomainModelTransformer`s are nearly identical. The only difference being the particular `Presenter` for which the data is intended. When updating the `DomainModel`s, we just provide them with our entity's `appTheme` field. That's it! To allow the `Presenter`s to subscribe to `DomainModel` updates, we also need to provide the `DomainModelTransformer`s in the constructor of the Use Case. Add the optional `transformers` argument to the constructor, and pass in the two `DomainModelTransformer`s within a list: #### File: `lib/features/home/domain/home_use_case.dart` + ```dart class HomeUseCase extends UseCase { HomeUseCase() @@ -198,6 +206,7 @@ Great, now that our domain layer is complete, let's move on to the necessary `Pr ##### Presentation To start, if you haven't already, create the six files laid out at the beginning of the guide: + 1. `presentation/home_view_model.dart`, 2. `presentation/home_presenter.dart`, 3. `presentation/home_ui.dart`, @@ -208,6 +217,7 @@ To start, if you haven't already, create the six files laid out at the beginning We'll outline each file one by one below, starting with the main `ViewModel`. #### File: `lib/features/home/presentation/home_view_model.dart` + ```dart import 'package:clean_framework/clean_framework.dart'; import 'package:flutter/material.dart'; @@ -233,6 +243,7 @@ class HomeViewModel extends ViewModel { For our main view model, we'll need the theme choice and a callback to the domain for updating the theme `onThemeChange`. #### File: `lib/features/home/presentation/home_presenter.dart` + ```dart import 'package:clean_framework/clean_framework.dart'; import 'package:flutter/material.dart'; @@ -267,6 +278,7 @@ class HomePresenter This will be the presenter for the main UI class (`home_ui.dart`). We call `useCase.getTheme()` on initialization `onLayoutReady` to update the theme with the default choice. #### File: `lib/features/home/presentation/home_ui.dart` + ```dart import 'package:clean_framework/clean_framework.dart'; import 'package:flutter/material.dart'; @@ -344,6 +356,7 @@ This will be our main UI that provides the user with radio button choices for th Next, we'll move on to creating the theme wrapper widget presentation code, starting with the view model. #### File: `lib/features/home/presentation/theme/home_theme_view_model.dart` + ```dart import 'package:clean_framework/clean_framework.dart'; import 'package:theme_example/features/home/domain/home_entity.dart'; @@ -365,6 +378,7 @@ class HomeThemeViewModel extends ViewModel { In this case, our theme widget only needs the current theme from the domain layer, so that's all the view model will contain. #### File: `lib/features/home/presentation/theme/home_theme_presenter.dart` + ```dart import 'package:clean_framework/clean_framework.dart'; import 'package:flutter/material.dart'; @@ -398,6 +412,7 @@ class HomeThemePresenter extends Presenter { } } ``` + That's it for the UI! In order to use the feature's `appTheme` choice outside of the app, we are providing a builder function that will convert the user's `AppTheme` choice to a `ThemeMode` object and passing it in as an argument to our `ExampleThemeModeWrapper` widget. Then, we can wrap `MaterialApp.router` in this widget and provide it with the user's `AppTheme` choice. Before creating the `ThemeExampleApp` class, we'll need to get a few prerequisites added. @@ -442,6 +458,7 @@ Before creating the `ThemeExampleApp` class, we'll need to get a few prerequisit Firstly, create a `UseCaseProvider` for our home feature: #### File: `lib/providers.dart` + ```dart import 'package:clean_framework/clean_framework.dart'; import 'package:theme_example/features/home/domain/home_use_case.dart'; @@ -454,6 +471,7 @@ final homeUseCaseProvider = UseCaseProvider( And finally, we'll need a router for our app. #### File: `lib/router.dart` + ```dart import 'package:clean_framework_router/clean_framework_router.dart'; import 'package:theme_example/features/home/presentation/home_ui.dart'; @@ -466,7 +484,7 @@ class ExampleRouter extends AppRouter { routes: [ AppRoute( route: Routes.home, - builder: (_, __) => HomeUI(), + builder: (_, _) => HomeUI(), ), ], ); @@ -486,6 +504,7 @@ enum Routes with RoutesMixin { Now that that's done, let's create our top-level widget to pull it all together. Create a new folder in `lib` called `app`, and create a new file in it called `theme_example_app.dart`: #### File: `lib/app/theme_example_app.dart` + ```dart import 'package:clean_framework/clean_framework.dart'; import 'package:clean_framework_router/clean_framework_router.dart'; @@ -532,4 +551,4 @@ This is where our theme actually gets applied to the app as a whole. Notice that Now, if you call `runApp(const ThemeExampleApp())` from within `lib/main.dart`, the app should run and allow the theme to be updated from our example feature. -We're done! Hopefully this gave you an understanding of some of the ways in which you can step outside the confines of a feature to interact with other parts of your app using Clean Framework. In the next part, we'll be continuing on with our Pokemon app and begin writing the external interface layer, allowing features to communicate with external services. \ No newline at end of file +We're done! Hopefully this gave you an understanding of some of the ways in which you can step outside the confines of a feature to interact with other parts of your app using Clean Framework. In the next part, we'll be continuing on with our Pokemon app and begin writing the external interface layer, allowing features to communicate with external services. diff --git a/packages/clean_framework/example/lib/routing/poke_router.dart b/packages/clean_framework/example/lib/routing/poke_router.dart index 564b3d19..95773979 100644 --- a/packages/clean_framework/example/lib/routing/poke_router.dart +++ b/packages/clean_framework/example/lib/routing/poke_router.dart @@ -14,11 +14,11 @@ class PokeRouter extends AppRouter { routes: [ AppRoute( route: Routes.home, - builder: (_, __) => HomeUI(), + builder: (_, _) => HomeUI(), routes: [ AppRoute( route: Routes.form, - builder: (_, __) => FormUI(), + builder: (_, _) => FormUI(), ), AppRoute.custom( route: Routes.profile, diff --git a/packages/clean_framework/example/test/features/profile/external_interface/pokemon_species_gateway_test.dart b/packages/clean_framework/example/test/features/profile/external_interface/pokemon_species_gateway_test.dart index 29005525..5f2fd2ca 100644 --- a/packages/clean_framework/example/test/features/profile/external_interface/pokemon_species_gateway_test.dart +++ b/packages/clean_framework/example/test/features/profile/external_interface/pokemon_species_gateway_test.dart @@ -1,3 +1,5 @@ +// ignore_for_file: lines_longer_than_80_chars + import 'package:clean_framework/clean_framework.dart'; import 'package:clean_framework_example_rest/core/pokemon/pokemon_success_response.dart'; import 'package:clean_framework_example_rest/features/profile/domain/profile_domain_models.dart'; @@ -29,7 +31,9 @@ void main() { 'flavor_text_entries': [ { 'flavor_text': - 'It keeps its tail\nraised to monitor\nits surroundings.\fIf you yank its\ntail, it will try\nto bite you.', + 'It keeps its tail\nraised to monitor\nits ' + 'surroundings.\fIf you yank its\ntail, ' + 'it will try\nto bite you.', 'language': { 'name': 'en', 'url': 'https://pokeapi.co/api/v2/language/9/', @@ -41,7 +45,9 @@ void main() { }, { 'flavor_text': - 'This intelligent\nPOKéMON roasts\nhard BERRIES with\felectricity to\nmake them tender\nenough to eat.', + 'This intelligent\nPOKéMON ' + 'roasts\nhard BERRIES with\felectricity to\nmake ' + 'them tender\nenough to eat.', 'language': { 'name': 'en', 'url': 'https://pokeapi.co/api/v2/language/9/', diff --git a/packages/clean_framework/example/test/flow_test.dart b/packages/clean_framework/example/test/flow_test.dart index f15612b2..906e0c6a 100644 --- a/packages/clean_framework/example/test/flow_test.dart +++ b/packages/clean_framework/example/test/flow_test.dart @@ -1,3 +1,5 @@ +// ignore_for_file: lines_longer_than_80_chars + import 'package:clean_framework/clean_framework.dart'; import 'package:clean_framework_example_rest/core/pokemon/pokemon_external_interface.dart'; import 'package:clean_framework_example_rest/core/pokemon/pokemon_request.dart'; diff --git a/packages/clean_framework/lib/src/presentation/presenter/presenter.dart b/packages/clean_framework/lib/src/presentation/presenter/presenter.dart index b6466494..e3fad074 100644 --- a/packages/clean_framework/lib/src/presentation/presenter/presenter.dart +++ b/packages/clean_framework/lib/src/presentation/presenter/presenter.dart @@ -1,26 +1,32 @@ +import 'dart:async'; + import 'package:clean_framework/src/core/core.dart'; import 'package:clean_framework/src/presentation/presenter/view_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:meta/meta.dart'; -abstract class Presenter extends ConsumerStatefulWidget { +abstract class Presenter< + V extends ViewModel, + M extends DomainModel, + U extends UseCase +> + extends ConsumerStatefulWidget { const Presenter({ required this.provider, required this.builder, super.key, - }) : _arg = const _NoArg(), - _family = null; + }) : _arg = const _NoArg(), + _family = null; Presenter.family({ required UseCaseProviderFamilyBase family, required Object arg, required this.builder, super.key, - }) : _arg = arg, - provider = family(arg), - _family = family; + }) : _arg = arg, + provider = family(arg), + _family = family; @visibleForTesting final UseCaseProviderBase provider; @@ -65,8 +71,7 @@ abstract class Presenter output, V viewModel, - ) => - onDomainModel(context, output, viewModel); + ) => onDomainModel(context, output, viewModel); /// Called whenever the presenter configuration changes. @protected @@ -84,8 +89,12 @@ abstract class Presenter provider.subscribe(ref); } -class _PresenterState extends ConsumerState> { +class _PresenterState< + V extends ViewModel, + M extends DomainModel, + U extends UseCase +> + extends ConsumerState> { U? _useCase; late final UseCaseProviderBase _provider; @@ -142,15 +151,18 @@ class _PresenterState setLayoutReadyIfViewModelFound()) - ..init(); + unawaited( + provider.notifier.first.then((_) => setLayoutReadyIfViewModelFound()), + ); + provider.init(); } else { - widget._family! - ..notifier + final family = widget._family!; + unawaited( + family.notifier .firstWhere((e) => e.$2 == arg) - .then((_) => setLayoutReadyIfViewModelFound()) - ..init(arg); + .then((_) => setLayoutReadyIfViewModelFound()), + ); + family.init(arg); } return provider; @@ -212,8 +224,8 @@ class ViewModelScope extends InheritedWidget { } static V? maybeOf(BuildContext context) { - final result = - context.dependOnInheritedWidgetOfExactType>(); + final result = context + .dependOnInheritedWidgetOfExactType>(); return result?.viewModel; } diff --git a/packages/clean_framework/lib/src/utilities/either.dart b/packages/clean_framework/lib/src/utilities/either.dart index 4ef52323..17a109e9 100644 --- a/packages/clean_framework/lib/src/utilities/either.dart +++ b/packages/clean_framework/lib/src/utilities/either.dart @@ -12,7 +12,6 @@ typedef EitherMapper = T Function(E); @sealed @immutable - /// [Either] represents a value of two possible types. /// An Either is either an [Either.left] or an [Either.right]. abstract class Either { @@ -46,7 +45,7 @@ abstract class Either { /// Folds either the left or the right side of this disjunction. T fold(EitherMapper leftMapper, EitherMapper rightMapper); - Never _noSuchElementException(value) { + Never _noSuchElementException(dynamic value) { throw NoSuchElementException( 'You should check ${isLeft ? 'isLeft' : 'isRight'} before calling.', ); diff --git a/packages/clean_framework/test/providers/providers_test.dart b/packages/clean_framework/test/providers/providers_test.dart index 73512697..ef2a0b2d 100644 --- a/packages/clean_framework/test/providers/providers_test.dart +++ b/packages/clean_framework/test/providers/providers_test.dart @@ -7,7 +7,7 @@ void main() { await tester.pumpWidget( AppProvidersContainer( child: const MaterialApp(), - onBuild: (_, __) {}, + onBuild: (_, _) {}, ), ); }); @@ -24,8 +24,9 @@ void main() { final provider = UseCaseProvider((_) => useCase); final gatewayProvider = GatewayProvider((_) => gateway); final bridgeGatewayProvider = BridgeGatewayProvider((_) => bridgeGateway); - final externalInterfaceProvider = - ExternalInterfaceProvider((_) => externalInterface); + final externalInterfaceProvider = ExternalInterfaceProvider( + (_) => externalInterface, + ); expect(provider.getUseCaseFromContext(context), useCase); expect(gatewayProvider.getGateway(context), gateway); @@ -39,16 +40,18 @@ void main() { test('Providers with overrides', () async { final provider = UseCaseProvider((_) => TestUseCase(TestEntity())); - final gatewayProvider = - GatewayProvider((_) => TestGateway(TestUseCase(TestEntity()))); + final gatewayProvider = GatewayProvider( + (_) => TestGateway(TestUseCase(TestEntity())), + ); final bridgeGatewayProvider = BridgeGatewayProvider( (_) => TestBridgeGateway( subscriberUseCase: TestUseCase(TestEntity()), publisherUseCase: TestUseCase(TestEntity()), ), ); - final externalInterfaceProvider = - ExternalInterfaceProvider((_) => TestInterface()); + final externalInterfaceProvider = ExternalInterfaceProvider( + (_) => TestInterface(), + ); final useCase = TestUseCase(TestEntity()); final gateway = TestGateway(useCase); @@ -92,8 +95,9 @@ class TestInterface extends ExternalInterface { } } -class TestBridgeGateway extends BridgeGateway { +class TestBridgeGateway + extends + BridgeGateway { TestBridgeGateway({ required super.subscriberUseCase, required super.publisherUseCase, @@ -124,10 +128,10 @@ class TestUseCase extends UseCase { TestUseCase(TestEntity entity) : super(entity: entity); void doRequest() => request( - TestDomainModel(), - onSuccess: (_) => TestEntity(), - onFailure: (_) => TestEntity(), - ); + TestDomainModel(), + onSuccess: (_) => TestEntity(), + onFailure: (_) => TestEntity(), + ); } class TestEntity extends Entity { diff --git a/packages/clean_framework/theme_example/lib/features/home/presentation/home_ui.dart b/packages/clean_framework/theme_example/lib/features/home/presentation/home_ui.dart index 71453870..3be4ab77 100644 --- a/packages/clean_framework/theme_example/lib/features/home/presentation/home_ui.dart +++ b/packages/clean_framework/theme_example/lib/features/home/presentation/home_ui.dart @@ -5,9 +5,7 @@ import 'package:theme_example/features/home/presentation/home_presenter.dart'; import 'package:theme_example/features/home/presentation/home_view_model.dart'; class HomeUI extends UI { - HomeUI({ - super.key, - }); + HomeUI({super.key}); @override HomePresenter create(WidgetBuilder builder) { @@ -26,36 +24,31 @@ class HomeUI extends UI { child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ - const SizedBox( - height: 36, - ), - Text( - 'Select App Theme', - style: themeData.textTheme.labelMedium, - ), + const SizedBox(height: 36), + Text('Select App Theme', style: themeData.textTheme.labelMedium), ...AppTheme.values.map( (theme) => Padding( padding: const EdgeInsets.symmetric(vertical: 8), - child: RadioListTile( - value: theme, + child: RadioGroup( groupValue: viewModel.appTheme, - title: Text( - theme.name, - style: themeData.textTheme.labelMedium!.copyWith( - color: theme == viewModel.appTheme - ? themeData.colorScheme.primary - : themeData.colorScheme.onSurface, - ), - ), onChanged: viewModel.onThemeChange, - contentPadding: const EdgeInsets.all(4), - tileColor: theme == viewModel.appTheme - ? themeData.colorScheme.primaryContainer - : null, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - side: BorderSide( - color: themeData.colorScheme.outline, + child: RadioListTile( + value: theme, + title: Text( + theme.name, + style: themeData.textTheme.labelMedium!.copyWith( + color: theme == viewModel.appTheme + ? themeData.colorScheme.primary + : themeData.colorScheme.onSurface, + ), + ), + contentPadding: const EdgeInsets.all(4), + tileColor: theme == viewModel.appTheme + ? themeData.colorScheme.primaryContainer + : null, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + side: BorderSide(color: themeData.colorScheme.outline), ), ), ), diff --git a/packages/clean_framework/theme_example/lib/router.dart b/packages/clean_framework/theme_example/lib/router.dart index 3c749817..373422b3 100644 --- a/packages/clean_framework/theme_example/lib/router.dart +++ b/packages/clean_framework/theme_example/lib/router.dart @@ -6,12 +6,7 @@ class ExampleRouter extends AppRouter { RouterConfiguration configureRouter() { return RouterConfiguration( debugLogDiagnostics: true, - routes: [ - AppRoute( - route: Routes.home, - builder: (_, __) => HomeUI(), - ), - ], + routes: [AppRoute(route: Routes.home, builder: (_, _) => HomeUI())], ); } } diff --git a/packages/clean_framework_firestore/lib/src/firebase_client_fake.dart b/packages/clean_framework_firestore/lib/src/firebase_client_fake.dart index 4f8ad9cf..057e9968 100644 --- a/packages/clean_framework_firestore/lib/src/firebase_client_fake.dart +++ b/packages/clean_framework_firestore/lib/src/firebase_client_fake.dart @@ -21,7 +21,7 @@ class FirebaseClientFake implements FirebaseClient { required String path, required String id, }) async { - if (_exception != null) throw _exception!; + if (_exception != null) throw _exception; return _content; } diff --git a/packages/clean_framework_firestore/test/firebase_external_interface_test.dart b/packages/clean_framework_firestore/test/firebase_external_interface_test.dart index 1b789145..5c5d4cab 100644 --- a/packages/clean_framework_firestore/test/firebase_external_interface_test.dart +++ b/packages/clean_framework_firestore/test/firebase_external_interface_test.dart @@ -26,8 +26,9 @@ void main() { firebaseClient: firebaseClient, ); - final result = await gateWay - .transport(const FirebaseWatchIdRequest(path: 'fake path', id: 'foo')); + final result = await gateWay.transport( + const FirebaseWatchIdRequest(path: 'fake path', id: 'foo'), + ); expect(result.isRight, isTrue); expect(result.right, FirebaseSuccessResponse(testContent)); @@ -73,8 +74,9 @@ void main() { firebaseClient: firebaseClient, ); - final result = await gateWay - .transport(const FirebaseReadIdRequest(path: 'fake path', id: 'foo')); + final result = await gateWay.transport( + const FirebaseReadIdRequest(path: 'fake path', id: 'foo'), + ); expect(result.isRight, isTrue); expect(result.right, FirebaseSuccessResponse(testContent)); }); @@ -86,8 +88,9 @@ void main() { firebaseClient: firebaseClient, ); - final result = await gateWay - .transport(const FirebaseReadAllRequest(path: 'fake path')); + final result = await gateWay.transport( + const FirebaseReadAllRequest(path: 'fake path'), + ); expect(result.isRight, isTrue); expect(result.right, FirebaseSuccessResponse(testContent)); }); @@ -100,8 +103,9 @@ void main() { firebaseClient: firebaseClient, ); - final result = await gateWay - .transport(const FirebaseWriteRequest(path: 'fake path', id: 'foo')); + final result = await gateWay.transport( + const FirebaseWriteRequest(path: 'fake path', id: 'foo'), + ); expect(result.isRight, isTrue); expect(result.right, const FirebaseSuccessResponse({'id': 'id'})); @@ -116,8 +120,9 @@ void main() { firebaseClient: firebaseClient, ); - final result = - await gateWay.transport(const FirebaseWriteRequest(path: 'fake path')); + final result = await gateWay.transport( + const FirebaseWriteRequest(path: 'fake path'), + ); expect(result.isRight, isTrue); expect(result.right, const FirebaseSuccessResponse({'id': 'id'})); @@ -131,8 +136,9 @@ void main() { firebaseClient: firebaseClient, ); - final result = await gateWay - .transport(const FirebaseUpdateRequest(path: 'fake path', id: 'foo')); + final result = await gateWay.transport( + const FirebaseUpdateRequest(path: 'fake path', id: 'foo'), + ); expect(result.isRight, isTrue); expect(result.right, const FirebaseSuccessResponse({})); }); @@ -144,8 +150,9 @@ void main() { firebaseClient: firebaseClient, ); - final result = await gateWay - .transport(const FirebaseDeleteRequest(path: 'fake path', id: 'foo')); + final result = await gateWay.transport( + const FirebaseDeleteRequest(path: 'fake path', id: 'foo'), + ); expect(result.isRight, isTrue); expect(result.right, const FirebaseSuccessResponse({})); }); @@ -159,8 +166,9 @@ void main() { firebaseClient: firebaseClient, ); - final result = await gateWay - .transport(const FirebaseReadIdRequest(path: 'fake path', id: 'foo')); + final result = await gateWay.transport( + const FirebaseReadIdRequest(path: 'fake path', id: 'foo'), + ); expect(result.isLeft, isTrue); expect( result.left, @@ -179,8 +187,9 @@ void main() { firebaseClient: firebaseClient, ); - final result = await gateWay - .transport(const FirebaseReadAllRequest(path: 'fake path')); + final result = await gateWay.transport( + const FirebaseReadAllRequest(path: 'fake path'), + ); expect(result.isLeft, isTrue); expect( result.left, @@ -198,8 +207,9 @@ void main() { firebaseClient: firebaseClient, ); - final result = await gateWay - .transport(const FirebaseWriteRequest(path: 'fake path', id: 'foo')); + final result = await gateWay.transport( + const FirebaseWriteRequest(path: 'fake path', id: 'foo'), + ); expect(result.isLeft, isTrue); expect( result.left, @@ -211,7 +221,7 @@ void main() { test('FirebaseExternalInterface query', () async { FirebaseClientFake({}) - ..createQuery('fake', (_) => _) + ..createQuery('fake', (f) => f) ..clearQuery() ..dispose(); }); diff --git a/packages/clean_framework_graphql/graphql_example/lib/router.dart b/packages/clean_framework_graphql/graphql_example/lib/router.dart index e995a3a8..59c8ea89 100644 --- a/packages/clean_framework_graphql/graphql_example/lib/router.dart +++ b/packages/clean_framework_graphql/graphql_example/lib/router.dart @@ -6,12 +6,7 @@ class ExampleRouter extends AppRouter { RouterConfiguration configureRouter() { return RouterConfiguration( debugLogDiagnostics: true, - routes: [ - AppRoute( - route: Routes.home, - builder: (_, __) => HomeUI(), - ), - ], + routes: [AppRoute(route: Routes.home, builder: (_, _) => HomeUI())], ); } } diff --git a/packages/clean_framework_graphql/lib/src/graphql_logger.dart b/packages/clean_framework_graphql/lib/src/graphql_logger.dart index 6b5a1480..e126ad6d 100644 --- a/packages/clean_framework_graphql/lib/src/graphql_logger.dart +++ b/packages/clean_framework_graphql/lib/src/graphql_logger.dart @@ -1,5 +1,7 @@ // coverage:ignore-file +import 'dart:async'; + import 'package:clean_framework/clean_framework_legacy.dart' hide Request, Response; import 'package:gql/language.dart'; @@ -16,7 +18,7 @@ class GraphQLLoggerLink extends Link { @override Stream request(Request request, [NextLink? forward]) { - _logRequest(request); + unawaited(_logRequest(request)); return forward!(request).map(_responseMapper).handleError(_onError); } diff --git a/packages/clean_framework_graphql/lib/src/graphql_service.dart b/packages/clean_framework_graphql/lib/src/graphql_service.dart index 975487bb..9a03af76 100644 --- a/packages/clean_framework_graphql/lib/src/graphql_service.dart +++ b/packages/clean_framework_graphql/lib/src/graphql_service.dart @@ -18,19 +18,21 @@ class GraphQLService { }) { final link = _createLink(token: token); - _createClient( - link: link, - persistence: persistence, - defaultPolicies: defaultPolicies, + unawaited( + _createClient( + link: link, + persistence: persistence, + defaultPolicies: defaultPolicies, + ), ); } GraphQLService.withClient({ required GraphQLClient client, this.timeout, - }) : endpoint = '', - headers = const {}, - _graphQLClient = client; + }) : endpoint = '', + headers = const {}, + _graphQLClient = client; /// The GraphQL endpoint. final String endpoint; @@ -75,15 +77,16 @@ class GraphQLService { GraphQLErrorPolicy? errorPolicy, }) async { final resolvedTimeout = timeout ?? this.timeout; - final policy = - fetchPolicy == null ? null : FetchPolicy.values[fetchPolicy.index]; + final policy = fetchPolicy == null + ? null + : FetchPolicy.values[fetchPolicy.index]; final doc = gql(document); final hasStitching = _hasStitching(doc); final errPolicy = errorPolicy == null ? hasStitching - ? ErrorPolicy.all - : ErrorPolicy.none + ? ErrorPolicy.all + : ErrorPolicy.none : ErrorPolicy.values[errorPolicy.index]; try { @@ -264,7 +267,7 @@ class GraphQLOperationError { final List? locations; /// Path of the error node in the query - final List? path; + final List? path; /// Implementation-specific extensions to this error final Map? extensions; diff --git a/packages/clean_framework_graphql/test/graphql_external_interface_test.dart b/packages/clean_framework_graphql/test/graphql_external_interface_test.dart index 39f1375c..7d5baef9 100644 --- a/packages/clean_framework_graphql/test/graphql_external_interface_test.dart +++ b/packages/clean_framework_graphql/test/graphql_external_interface_test.dart @@ -168,11 +168,11 @@ void main() { class GraphQLServiceFake extends Fake implements GraphQLService { GraphQLServiceFake(this._json) - : _exception = _json.isEmpty ? 'service exception' : null; + : _exception = _json.isEmpty ? 'service exception' : null; GraphQLServiceFake.exception(GraphQLServiceException exception) - : _exception = exception, - _json = const {}; + : _exception = exception, + _json = const {}; final Map _json; final Object? _exception; @@ -186,7 +186,7 @@ class GraphQLServiceFake extends Fake implements GraphQLService { GraphQLFetchPolicy? fetchPolicy, GraphQLErrorPolicy? errorPolicy, }) async { - if (_exception != null) throw _exception!; + if (_exception != null) throw _exception; return GraphQLServiceResponse( data: _json, diff --git a/packages/clean_framework_http/example/lib/routing/clean_framework_http_example_router.dart b/packages/clean_framework_http/example/lib/routing/clean_framework_http_example_router.dart index b0f15391..a4fa5829 100644 --- a/packages/clean_framework_http/example/lib/routing/clean_framework_http_example_router.dart +++ b/packages/clean_framework_http/example/lib/routing/clean_framework_http_example_router.dart @@ -9,7 +9,7 @@ class CleanFrameworkHttpExampleRouter extends AppRouter { routes: [ AppRoute( route: Routes.home, - builder: (_, __) => HomeUI(), + builder: (_, _) => HomeUI(), ), ], ); diff --git a/packages/clean_framework_router/lib/src/app_route.dart b/packages/clean_framework_router/lib/src/app_route.dart index df99588f..a0dbf7d5 100644 --- a/packages/clean_framework_router/lib/src/app_route.dart +++ b/packages/clean_framework_router/lib/src/app_route.dart @@ -18,12 +18,12 @@ class AppRoute extends GoRoute { super.routes, super.redirect, }) : super( - path: route.path, - name: (route as Enum).name, - builder: builder == null - ? null - : (ctx, state) => builder(ctx, AppRouterState.from(state)), - ); + path: route.path, + name: (route as Enum).name, + builder: builder == null + ? null + : (ctx, state) => builder(ctx, AppRouterState.from(state)), + ); AppRoute.page({ required this.route, @@ -33,12 +33,12 @@ class AppRoute extends GoRoute { super.routes, super.redirect, }) : super( - path: route.path, - name: (route as Enum).name, - pageBuilder: builder == null - ? null - : (ctx, state) => builder(ctx, AppRouterState.from(state)), - ); + path: route.path, + name: (route as Enum).name, + pageBuilder: builder == null + ? null + : (ctx, state) => builder(ctx, AppRouterState.from(state)), + ); AppRoute.custom({ required this.route, @@ -49,20 +49,20 @@ class AppRoute extends GoRoute { super.routes, super.redirect, }) : super( - path: route.path, - name: (route as Enum).name, - pageBuilder: builder == null - ? null - : (context, state) { - final transBuilder = - transitionsBuilder ?? (_, __, ___, child) => child; + path: route.path, + name: (route as Enum).name, + pageBuilder: builder == null + ? null + : (context, state) { + final transBuilder = + transitionsBuilder ?? (_, _, _, child) => child; - return CustomTransitionPage( - child: builder(context, AppRouterState.from(state)), - transitionsBuilder: transBuilder, - ); - }, - ); + return CustomTransitionPage( + child: builder(context, AppRouterState.from(state)), + transitionsBuilder: transBuilder, + ); + }, + ); final RoutesMixin route; } diff --git a/packages/clean_framework_router/test/app_router_test.dart b/packages/clean_framework_router/test/app_router_test.dart index 873fc5f0..522a40f5 100644 --- a/packages/clean_framework_router/test/app_router_test.dart +++ b/packages/clean_framework_router/test/app_router_test.dart @@ -36,7 +36,7 @@ class TestRouter extends AppRouter { RouterConfiguration configureRouter() { return RouterConfiguration( routes: routes, - errorBuilder: (_, __) => const Page404(), + errorBuilder: (_, _) => const Page404(), redirect: redirect, observers: observers, ); @@ -52,11 +52,11 @@ void main() { routes: [ AppRoute( route: Routes.home, - builder: (_, __) => const OnTapPage(id: 'Home'), + builder: (_, _) => const OnTapPage(id: 'Home'), ), AppRoute( route: Routes.detail, - builder: (_, __) => const OnTapPage(id: 'Detail'), + builder: (_, _) => const OnTapPage(id: 'Detail'), ), ], ); @@ -73,14 +73,14 @@ void main() { routes: [ AppRoute( route: Routes.home, - builder: (_, __) => OnTapPage( + builder: (_, _) => OnTapPage( id: 'Home', onTap: (context) => testRouter.go(Routes.detail), ), ), AppRoute( route: Routes.detail, - builder: (_, __) => const OnTapPage(id: 'Detail'), + builder: (_, _) => const OnTapPage(id: 'Detail'), ), ], ); @@ -114,14 +114,14 @@ void main() { routes: [ AppRoute( route: Routes.home, - builder: (_, __) => OnTapPage( + builder: (_, _) => OnTapPage( id: 'Home', onTap: (context) => testRouter.go(Routes.subDetail), ), routes: [ AppRoute( route: Routes.subDetail, - builder: (_, __) => const OnTapPage(id: 'Detail'), + builder: (_, _) => const OnTapPage(id: 'Detail'), ), ], ), @@ -157,7 +157,7 @@ void main() { routes: [ AppRoute( route: Routes.home, - builder: (_, __) => OnTapPage( + builder: (_, _) => OnTapPage( id: 'Home', onTap: (context) => testRouter.go( Routes.detailWithParam, @@ -198,7 +198,7 @@ void main() { routes: [ AppRoute( route: Routes.home, - builder: (_, __) => OnTapPage( + builder: (_, _) => OnTapPage( id: 'Home', // ignore: deprecated_member_use_from_same_package onTap: (context) => testRouter.to( @@ -240,7 +240,7 @@ void main() { routes: [ AppRoute( route: Routes.home, - builder: (_, __) => OnTapPage( + builder: (_, _) => OnTapPage( id: 'Home', onTap: (context) => testRouter.go( Routes.detail, @@ -281,7 +281,7 @@ void main() { routes: [ AppRoute( route: Routes.home, - builder: (_, __) => OnTapPage( + builder: (_, _) => OnTapPage( id: 'Home', onTap: (context) => testRouter.go( Routes.detail, @@ -323,7 +323,7 @@ void main() { routes: [ AppRoute( route: Routes.home, - builder: (_, __) => OnTapPage( + builder: (_, _) => OnTapPage( id: 'Home', onTap: (context) => testRouter.go( Routes.detailWithParam, @@ -337,7 +337,8 @@ void main() { route: Routes.detailWithParam, builder: (_, state) => OnTapPage( id: 'Detail', - value: '${state.params['meta']}${state.queryParams['b']}' + value: + '${state.params['meta']}${state.queryParams['b']}' '${state.extra}', ), ), @@ -366,7 +367,7 @@ void main() { routes: [ AppRoute( route: Routes.home, - builder: (_, __) => OnTapPage( + builder: (_, _) => OnTapPage( id: 'Home', onTap: (context) => testRouter.go(Routes.subDetail), ), @@ -437,7 +438,7 @@ void main() { routes: [ AppRoute( route: Routes.home, - builder: (_, __) => OnTapPage( + builder: (_, _) => OnTapPage( id: 'Home', onTap: (context) => testRouter.push( Routes.subDetailWithParam, @@ -451,7 +452,8 @@ void main() { route: Routes.subDetailWithParam, builder: (_, state) => OnTapPage( id: 'Detail', - value: '${state.params['meta']}${state.queryParams['b']}' + value: + '${state.params['meta']}${state.queryParams['b']}' '${state.extra}', ), ), @@ -486,7 +488,7 @@ void main() { routes: [ AppRoute( route: Routes.home, - builder: (_, __) => OnTapPage( + builder: (_, _) => OnTapPage( id: 'Home', onTap: (context) => testRouter.push(Routes.subDetail), ), @@ -548,7 +550,7 @@ void main() { routes: [ AppRoute( route: Routes.home, - builder: (_, __) => OnTapPage( + builder: (_, _) => OnTapPage( id: 'Home', onTap: (context) => testRouter.push(Routes.subDetail), ), @@ -610,7 +612,7 @@ void main() { routes: [ AppRoute( route: Routes.home, - builder: (_, __) => OnTapPage( + builder: (_, _) => OnTapPage( id: 'Home', onTap: (context) => testRouter.push(Routes.subDetail), ), @@ -680,7 +682,7 @@ void main() { routes: [ AppRoute( route: Routes.home, - builder: (_, __) => OnTapPage( + builder: (_, _) => OnTapPage( id: 'Home', onTap: (context) => testRouter.goLocation( '/detail/123?b=456', @@ -692,7 +694,8 @@ void main() { route: Routes.subDetailWithParam, builder: (_, state) => OnTapPage( id: 'Detail', - value: '${state.params['meta']}${state.queryParams['b']}' + value: + '${state.params['meta']}${state.queryParams['b']}' '${state.extra}', ), ), @@ -728,7 +731,7 @@ void main() { routes: [ AppRoute( route: Routes.home, - builder: (_, __) => OnTapPage( + builder: (_, _) => OnTapPage( id: 'Home', // ignore: deprecated_member_use_from_same_package onTap: (context) => testRouter.open( @@ -741,7 +744,8 @@ void main() { route: Routes.subDetailWithParam, builder: (_, state) => OnTapPage( id: 'Detail', - value: '${state.params['meta']}${state.queryParams['b']}' + value: + '${state.params['meta']}${state.queryParams['b']}' '${state.extra}', ), ), @@ -775,7 +779,7 @@ void main() { routes: [ AppRoute( route: Routes.home, - builder: (_, __) => OnTapPage( + builder: (_, _) => OnTapPage( id: 'Home', onTap: (context) => testRouter.goLocation('/test'), ), @@ -804,14 +808,14 @@ void main() { routes: [ AppRoute( route: Routes.home, - builder: (_, __) => OnTapPage( + builder: (_, _) => OnTapPage( id: 'Home', onTap: (context) => testRouter.go(Routes.subDetail), ), routes: [ AppRoute( route: Routes.subDetail, - builder: (_, __) => const OnTapPage(id: 'Detail'), + builder: (_, _) => const OnTapPage(id: 'Detail'), ), ], ), @@ -842,19 +846,19 @@ void main() { routes: [ AppRoute( route: Routes.home, - builder: (_, __) => OnTapPage( + builder: (_, _) => OnTapPage( id: 'Home', onTap: (context) => testRouter.go(Routes.detail), ), ), AppRoute( route: Routes.detail, - builder: (_, __) => const OnTapPage(id: 'Detail'), + builder: (_, _) => const OnTapPage(id: 'Detail'), redirect: (context, state) => '/more-detail', ), AppRoute( route: Routes.moreDetailRoot, - builder: (_, __) => const OnTapPage(id: 'More Detail'), + builder: (_, _) => const OnTapPage(id: 'More Detail'), ), ], ); @@ -883,18 +887,18 @@ void main() { routes: [ AppRoute( route: Routes.home, - builder: (_, __) => OnTapPage( + builder: (_, _) => OnTapPage( id: 'Home', onTap: (context) => testRouter.go(Routes.detail), ), ), AppRoute( route: Routes.detail, - builder: (_, __) => const OnTapPage(id: 'Detail'), + builder: (_, _) => const OnTapPage(id: 'Detail'), ), AppRoute( route: Routes.moreDetailRoot, - builder: (_, __) => const OnTapPage(id: 'More Detail'), + builder: (_, _) => const OnTapPage(id: 'More Detail'), ), ], redirect: (context, state) { @@ -927,21 +931,21 @@ void main() { routes: [ AppRoute( route: Routes.home, - builder: (_, __) => OnTapPage( + builder: (_, _) => OnTapPage( id: 'Home', onTap: (context) => testRouter.go(Routes.detail), ), ), AppRoute( route: Routes.detail, - builder: (_, __) => OnTapPage( + builder: (_, _) => OnTapPage( id: 'Detail', onTap: (context) => testRouter.go(Routes.moreDetailRoot), ), ), AppRoute( route: Routes.moreDetailRoot, - builder: (_, __) => const OnTapPage(id: 'More Detail'), + builder: (_, _) => const OnTapPage(id: 'More Detail'), ), ], ); @@ -995,21 +999,21 @@ void main() { routes: [ AppRoute( route: Routes.home, - builder: (_, __) => OnTapPage( + builder: (_, _) => OnTapPage( id: 'Home', onTap: (context) => testRouter.go(Routes.detail), ), ), AppRoute( route: Routes.detail, - builder: (_, __) => OnTapPage( + builder: (_, _) => OnTapPage( id: 'Detail', onTap: (context) => testRouter.go(Routes.moreDetailRoot), ), ), AppRoute( route: Routes.moreDetailRoot, - builder: (_, __) => const OnTapPage(id: 'More Detail'), + builder: (_, _) => const OnTapPage(id: 'More Detail'), ), ], ); @@ -1054,7 +1058,7 @@ void main() { routes: [ AppRoute.page( route: Routes.home, - builder: (_, __) => CupertinoPage( + builder: (_, _) => CupertinoPage( child: OnTapPage( id: 'Home', onTap: (context) => testRouter.go( @@ -1070,7 +1074,8 @@ void main() { route: Routes.subDetailWithParam, builder: (_, state) => OnTapPage( id: 'Detail', - value: '${state.params['meta']}${state.queryParams['b']}' + value: + '${state.params['meta']}${state.queryParams['b']}' '${state.extra}', ), ), @@ -1109,7 +1114,7 @@ void main() { routes: [ AppRoute.custom( route: Routes.home, - builder: (_, __) => OnTapPage( + builder: (_, _) => OnTapPage( id: 'Home', onTap: (context) => testRouter.go( Routes.subDetailWithParam, @@ -1129,7 +1134,8 @@ void main() { route: Routes.subDetailWithParam, builder: (_, state) => OnTapPage( id: 'Detail', - value: '${state.params['meta']}${state.queryParams['b']}' + value: + '${state.params['meta']}${state.queryParams['b']}' '${state.extra}', ), ), @@ -1167,7 +1173,7 @@ void main() { routes: [ AppRoute( route: Routes.home, - builder: (_, __) => OnTapPage( + builder: (_, _) => OnTapPage( id: 'Home', onTap: (context) => testRouter.go( Routes.subDetailWithParam, @@ -1181,7 +1187,8 @@ void main() { route: Routes.subDetailWithParam, builder: (_, state) => OnTapPage( id: 'Detail', - value: '${state.params['meta']}${state.queryParams['b']}' + value: + '${state.params['meta']}${state.queryParams['b']}' '${state.extra}', ), ), @@ -1215,7 +1222,7 @@ void main() { routes: [ AppRoute( route: Routes.home, - builder: (_, __) => const OnTapPage(id: 'Home'), + builder: (_, _) => const OnTapPage(id: 'Home'), ), ], ); diff --git a/packages/clean_framework_test/lib/src/presenter_test.dart b/packages/clean_framework_test/lib/src/presenter_test.dart index 2bb2df23..56245809 100644 --- a/packages/clean_framework_test/lib/src/presenter_test.dart +++ b/packages/clean_framework_test/lib/src/presenter_test.dart @@ -12,8 +12,8 @@ import 'package:flutter_test/flutter_test.dart' as ft; import 'package:meta/meta.dart'; @isTest -void presenterTest( +void +presenterTest( String description, { required Presenter Function(WidgetBuilder builder) create, List overrides = const [], @@ -76,8 +76,11 @@ void presenterTest( +void presenterCallbackTest< + V extends ViewModel, + M extends DomainModel, + U extends UseCase +>( String description, { required U useCase, required Presenter Function(WidgetBuilder builder) create, @@ -116,12 +119,12 @@ class _TestBuilderState super.initState(); if (widget.onInit != null) { - SchedulerBinding.instance.addPostFrameCallback((_) { + SchedulerBinding.instance.addPostFrameCallback((_) async { // ignore: invalid_use_of_visible_for_testing_member final useCase = widget.presenter.provider.read( ProviderScope.containerOf(context), ); - widget.onInit!(useCase as U); + await widget.onInit!(useCase as U); }); } } From 2bc5eeda4506dd3575697a5f689ee86e90370bf9 Mon Sep 17 00:00:00 2001 From: Sarbagya Dhaubanjar Date: Tue, 13 Jan 2026 16:07:51 +0545 Subject: [PATCH 3/3] upgrade deps --- packages/clean_framework/pubspec.yaml | 4 ++-- packages/clean_framework_firestore/pubspec.yaml | 2 +- packages/clean_framework_graphql/pubspec.yaml | 4 ++-- packages/clean_framework_test/pubspec.yaml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/clean_framework/pubspec.yaml b/packages/clean_framework/pubspec.yaml index 15f9cbad..9e60972a 100644 --- a/packages/clean_framework/pubspec.yaml +++ b/packages/clean_framework/pubspec.yaml @@ -19,13 +19,13 @@ environment: resolution: workspace dependencies: - equatable: ^2.0.5 + equatable: ^2.0.8 flutter: sdk: flutter flutter_riverpod: ^3.1.0 meta: '>=1.12.0 <2.0.0' stack_trace: '>=1.11.0 <2.0.0' - uniform: ^2.0.6 + uniform: ^2.0.7 dev_dependencies: clean_framework_test: ^0.5.4 diff --git a/packages/clean_framework_firestore/pubspec.yaml b/packages/clean_framework_firestore/pubspec.yaml index 95eeb533..29ee3ac0 100644 --- a/packages/clean_framework_firestore/pubspec.yaml +++ b/packages/clean_framework_firestore/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: clean_framework: ^4.1.3 cloud_firestore: ^6.1.1 - equatable: ^2.0.5 + equatable: ^2.0.8 flutter: sdk: flutter diff --git a/packages/clean_framework_graphql/pubspec.yaml b/packages/clean_framework_graphql/pubspec.yaml index c43e6544..84e7f588 100644 --- a/packages/clean_framework_graphql/pubspec.yaml +++ b/packages/clean_framework_graphql/pubspec.yaml @@ -15,8 +15,8 @@ dependencies: flutter: sdk: flutter - gql: ^1.0.0 - graphql: ^5.1.3 + gql: ^1.0.1 + graphql: ^5.2.3 dev_dependencies: clean_framework_test: ^0.5.4 diff --git a/packages/clean_framework_test/pubspec.yaml b/packages/clean_framework_test/pubspec.yaml index 498abd6a..0f565585 100644 --- a/packages/clean_framework_test/pubspec.yaml +++ b/packages/clean_framework_test/pubspec.yaml @@ -21,7 +21,7 @@ dependencies: flutter_test: sdk: flutter meta: '>=1.8.0 <2.0.0' - mocktail: ^1.0.0 + mocktail: ^1.0.4 dev_dependencies: very_good_analysis: ^10.0.0