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/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/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/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/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/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/pubspec.yaml b/packages/clean_framework/pubspec.yaml
index efbe4721..9e60972a 100644
--- a/packages/clean_framework/pubspec.yaml
+++ b/packages/clean_framework/pubspec.yaml
@@ -13,17 +13,19 @@ 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
+ 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/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/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/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/pubspec.yaml b/packages/clean_framework_firestore/pubspec.yaml
index 9ba3eb8d..29ee3ac0 100644
--- a/packages/clean_framework_firestore/pubspec.yaml
+++ b/packages/clean_framework_firestore/pubspec.yaml
@@ -5,14 +5,16 @@ 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
cloud_firestore: ^6.1.1
- equatable: ^2.0.5
+ equatable: ^2.0.8
flutter:
sdk: flutter
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/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/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/pubspec.yaml b/packages/clean_framework_graphql/pubspec.yaml
index 0d6fa57f..84e7f588 100644
--- a/packages/clean_framework_graphql/pubspec.yaml
+++ b/packages/clean_framework_graphql/pubspec.yaml
@@ -5,16 +5,18 @@ 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
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_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_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/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/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_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);
});
}
}
diff --git a/packages/clean_framework_test/pubspec.yaml b/packages/clean_framework_test/pubspec.yaml
index 43faa7e1..0f565585 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
@@ -19,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
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.