Step-by-Step Guide to Integrate DCM with Open Source Dart & Flutter Packages: "Wolt Modal Sheet Use-case"
At DCM, we understand the importance of maintaining a high-quality codebase, especially in a project as widely used as the Wolt Modal Sheet package. This package provides developers with a powerful tool for implementing responsive modals with multiple pages, smooth transitions, and scrollable content within Flutter applications. However, as projects grow and evolve, ensuring code consistency and catching potential bugs early becomes increasingly critical, particularly when maintaining an open-source project that receives multiple pull requests from different contributors.
Before we start, we'd like to mention that we offer a free OSS license for Dart and Flutter open-source packages. Check out our pricing and contact us by clicking the "Looking for OSS license?" button.
In this guide, we will walk you through the process of integrating DCM into the Wolt Modal Sheet package, covering everything from the initial setup to configuring CI pipelines and addressing code issues.
Evaluating the Project Structure
Our first step was to assess the structure of the Wolt Modal Sheet package and determine whether the project operates as a monorepo or consists of multiple packages, each with its own analysis_options.yaml
file.
In monorepo structures, we recommend applying DCM configurations globally across all packages. This ensures consistency in code quality checks throughout the project.
To get started, we recommend adding the following configuration to the main analysis_options.yaml
file or to each individual package file:
dart_code_metrics:
extends:
- recommended
This configuration leverages our recommended rule preset, which prioritizes identifying potential bugs over enforcing stylistic preferences. It’s an excellent starting point, especially for projects that have been in development for some time, as it minimizes disruption while still improving code quality.
Configuring Melos for Monorepo Projects
Given that the Wolt Modal Sheet package might be part of a larger monorepo managed by Melos
, we evaluated how best to integrate DCM into this workflow. For monorepos, we suggest defining a custom script within your melos.yaml
file to automate DCM analysis across all packages.
Here’s what we recommend:
scripts:
dcm-analyze:
exec: dcm analyze .
With this script in place, you can use the melos analyze
command to trigger DCM, ensuring that all parts of the project are checked consistently for potential issues.
Setting Up Continuous Integration (CI) with GitHub Actions
To maintain a high standard of code quality across all contributions, integrating DCM into your continuous integration (CI) pipeline is essential. Since this open-source package uses GitHub Actions, we recommend setting up a GitHub Actions workflow that runs DCM analysis as part of your CI process.
Below is a configuration example we propose:
dcm-analyze:
name: Flutter Analyze
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-[email protected]
with:
flutter-version: '3.13.0'
channel: stable
- name: Get Flutter version
run: flutter --version
- name: Get dependencies
run: flutter pub get
- name: Setup Melos
run: dart pub global activate melos
- name: Melos bootstrap
run: melos bootstrap
- name: Install DCM
uses: CQLabs/setup-dcm@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Run DCM
run: dcm analyze . --fatal-style
By default, style issues will not be treated as fatal errors. To ensure that all style-level issues are treated as fatal, we will pass the --fatal-style
flag to the analyze command, making sure all types of issues are reported.
This setup ensures that DCM is run on every pull request, helping to identify and address potential issues before they can be merged into the main branch. This is a critical step in maintaining the overall health and quality of the Wolt Modal Sheet codebase, especially since it often receives PRs from external contributors.
Running DCM Locally: Identifying and Addressing Issues
After setting up DCM, we suggest running dcm analyze .
locally. This command will scan your codebase and generate a report, highlighting areas that may require attention.
During our assessment, we found that DCM excels at catching both stylistic issues and potential bugs. For example, by integrating DCM into the main library, but not yet into all examples and other sub-packages, we found 313
warning issues and 351
style issues. This is significant.
Luckily, DCM provides an auto fix feature. For issues that can be resolved automatically, we recommend using the dcm fix .
command. This will apply automatic fixes across the project, often resolving a significant number of issues without requiring manual intervention.
If your project is large or segmented, you can target specific areas by running commands like dcm fix lib
to focus on particular directories.
By running this auto-fix, we observed a significant reduction in both style and warning issues. The number of warning issues decreased from 313 to 235, and the style issues dropped from 351 to 65. This was a major improvement, allowing us to focus on the remaining issues that require manual attention.
Categorizing and Addressing Identified Issues
As you begin addressing the issues identified by DCM, we recommend categorizing them into two main groups:
1. Non-Critical or Stylistic Issues
Some rules may flag stylistic issues that aren’t critical to the functionality of your code. We found that these can often be disabled temporarily to reduce immediate disruption. This could be your strategy as well.
For example, the prefer-single-widget-per-file
rule might not align with your current code structure. In such cases, you can disable it in your analysis_options.yaml
:
dart_code_metrics:
extends:
- recommended
rules:
- prefer-single-widget-per-file: false
2. Critical or Potential Bugs
Other issues flagged by DCM may indicate potential bugs that could cause runtime errors. We recommend prioritizing these issues for immediate resolution. For example, if DCM flags a nullable interpolation error, it’s crucial to address it to prevent potential crashes. Here is one example from the original code:
RouteInformation? restoreRouteInformation(
PlaygroundRouterConfiguration configuration,
) {
if (configuration.isUnknown) {
return RouteInformation(uri: Uri.parse('/unknown'));
} else if (configuration.isHomePage) {
return RouteInformation(uri: Uri.parse('/'));
} else if (configuration.isSheetPage) {
final path = configuration.multiPagePathName?.queryParamName; // <-- potentially nullable
final pageIndex = configuration.pageIndex;
return RouteInformation(
uri: Uri.parse(
'/$sheetPageSegment?$pathQueryParam=$path&$pageIndexQueryParam=$pageIndex',
),
);
}
return null;
}
As you can see, the path
variable can be null, but it's used in Uri.parse
without a non-null check. This can potentially be a bug. Here’s how this issue can be addressed:
final path = configuration.multiPagePathName?.queryParamName;
if (path == null) {
return null;
}
This bug was caught by DCM from the recommended
preset, and that was only one example. We also found several improvements in readability and potential bug prevention. For example:
final deltaDiff = details.primaryDelta! / _childHeight;
if (_dismissDirection == WoltModalDismissDirection.down) {
_animationController.value -= deltaDiff;
} else if (_dismissDirection == WoltModalDismissDirection.up) {
_animationController.value += deltaDiff;
}
One of the rules suggested to change this if statement to a switch
:
final deltaDiff = details.primaryDelta! / _childHeight;
switch (_dismissDirection) {
case WoltModalDismissDirection.down:
_animationController.value -= deltaDiff;
break;
case WoltModalDismissDirection.up:
_animationController.value += deltaDiff;
break;
default:
break;
}
Using switch
improves readability and makes it easier to add more cases when needed. In certain cases you can also benefit from the exhaustiveness check provided by the analyzer (when it shows an error for each missing case).
There were several similar potential bugs and issues, which we left for the maintainers to address at their own pace. However, one critical rule we highlighted to the maintainer is always-remove-listener
. Receiving a warning for this rule suggests a potential memory leak. There are numerous instances in this repository where DCM caught this issue, and we recommended fixing them.
Below is one example where widget.controller
was never disposed of, potentially leading to a memory leak.
Refining Your DCM Configuration
As you address the identified issues, you might temporarily disable certain rules to focus on more pressing matters. This is a common approach during the initial integration phase.
However, we recommend gradually re-enabling these rules over time and addressing the associated issues. Here’s an example configuration that balances ongoing development with code quality improvements:
dart_code_metrics:
extends:
- recommended
rules:
- prefer-single-widget-per-file: false
- avoid-unsafe-collection-methods: false
- always-remove-listener: false
...
This approach allows your team to maintain a high standard of code quality without overwhelming your development process.
Unused Code and Unused Files
Next, we ran the following two commands on the Wolt package:
dcm check-unused-files .
The result was interesting — we found several files that are no longer used and can be safely removed.
⚠ unused file
wolt_di/lib/src/manager/dependency_container_registrar.dart
⚠ unused file
wolt_di/lib/src/manager/dependency_container_resolver.dart
✖ total unused files - 2
dcm check-unused-code .
Similar to the unused files, we also found several instances of dead code that can be removed. This was a significant and valuable improvement.
lib/src/content/components/paginating_group/wolt_modal_sheet_page_transition_state.dart:
⚠ unused method topBarOpacity
at lib/src/content/components/paginating_group/wolt_modal_sheet_page_transition_state.dart:153:3
lib/src/content/wolt_modal_sheet_layout.dart:
⚠ unused field woltModalType
at lib/src/content/wolt_modal_sheet_layout.dart:19:3
wolt_di/lib/src/manager/dependency_container_registrar.dart:
⚠ unused method registerContainerFactory
at wolt_di/lib/src/manager/dependency_container_registrar.dart:12:3
⚠ unused class DependencyContainerRegistrar
at wolt_di/lib/src/manager/dependency_container_registrar.dart:3:1
wolt_di/lib/src/manager/dependency_container_resolver.dart:
⚠ unused method subscribeToContainer
at wolt_di/lib/src/manager/dependency_container_resolver.dart:4:3
⚠ unused method unsubscribeFromContainer
at wolt_di/lib/src/manager/dependency_container_resolver.dart:20:3
⚠ unused method getDependencyContainer
at wolt_di/lib/src/manager/dependency_container_resolver.dart:34:3
⚠ unused class DependencyContainerResolver
at wolt_di/lib/src/manager/dependency_container_resolver.dart:1:1
✖ total unused code (top-level declarations and class members) - 8
Calculate Metrics
Another feature that can help with maintaining codebase is metrics. By enabling different metrics, you can timely spot overly complex places that require your immediate attention. Metrics are customizable and provide a comprehensive overview. For example, we enabled:
dart_code_metrics:
extends:
- recommended
metrics:
cyclomatic-complexity: 20
lines-of-code: 100
maximum-nesting-level: 5
number-of-used-widgets: 20
coupling-between-object-classes: 12
number-of-methods: 10
Then we ran the command dcm calculate-metrics --reporter=html --open .
to calculate the metrics, generate an HTML report, and open it for review. Here's what we found:
This provided a great overview, but we left the decision to the maintainer on how to configure the metrics or whether to keep them enabled.
Updating Documentation and Contribution Guidelines
To ensure that all contributors are aligned with the new standards, we strongly recommend updating your CONTRIBUTING.md
file. This should include instructions on how to work with DCM, ensuring consistency across contributions. Here’s an example section you might add:
Enabling and activating DCM is optional. When you open a PR, the CI bots will show you any DCM warnings introduced by your changes, which should be fixed before submitting. Please follow the instructions at <https://dcm.dev/pricing/> to use the Free tier or purchase a Pro license. Note that the Free tier doesn't support all rules, so you will also need to consult the output of the DCM workflow on GitHub when you open your PR.
This addition will help ensure that all contributors understand how to use DCM and follow the same code quality standards.
Preparing for Pull Requests
Finally, before submitting a pull request, we recommend that you thoroughly review your code to ensure it’s free of DCM issues. Providing a detailed description of the changes and linking to any related issues or discussions can also help reviewers understand the context of your changes.
For a good example of a detailed pull request, you might refer to this pull request in the Wolt Modal Sheet repository.
Conclusion
The key takeaway is that while integrating DCM may require some initial effort, employing the right strategies and a gradual approach can significantly benefit any project. By following the steps we took in the Wolt package, your project will experience notable improvements in both code quality and maintainability.
If you are an open-source maintainer and looking for an OSS license, go to our pricing page and contact us by clicking the "Looking for OSS license?" button.
Work in a team and interested in integrating DCM? Just contact us, we are here to help.