Danger System + GitHub Actions: Guardians of Flutter Code

Carlos Daniel
5 min readApr 26, 2024

This is a translation of the original post in Spanish.

You might be wondering: what is the Danger System? The answer is straightforward: A “Danger System” acts as an automated watchdog for our code in Ruby, JS, Kotlin, and other frameworks/languages. However, this article will focus on Flutter (thanks to Ruby).

Imagine that every time someone submits a change to the code (a “pull request”), Danger jumps into action to thoroughly review it.

What does Danger look for?

Specifically, three elements, though we could expand or reduce them, but I like to aim for these:

  • Style errors: Danger ensures that your code follows Flutter’s best practices, such as correct indentation, the use of descriptive variable names, and clear code organization. Thus, if we have a document with the Code Style, we can reference it.
  • Linting issues: Danger detects common programming errors that could cause future issues, such as unused variables, redundant code, or unhandled exceptions.
  • Incomplete unit tests: Danger checks that each part of the code is covered by unit tests, ensuring that the code works as expected and is resilient to future changes.

What happens if Danger finds something?

Danger simply posts a comment on the pull request, explaining what it found and how to fix it. This way, everyone on the team can learn from the mistakes and collectively improve the quality of the code.

What do I need to set up the Danger System for my app?
To get started with Danger in a Flutter application, you first need to install Danger in your development environment.

Here are the general steps you should follow:

  1. Update Ruby: if we’re on Mac, Ruby is installed by default, but we can update its version using brewand update our PATHwith this new installation.
  2. Install Bundler: Bundler is a tool to manage dependencies in Ruby projects. We can do this by running gem install bundler in the terminal.
  3. Create a Gemfile: At the root of the Flutter project, create a file called Gemfile. Personally, since I work with GitHub, I like to create a .github folder and place the Gemfile there.
  4. Specify the Danger dependencies: Within the Gemfile, define the necessary gem dependencies for Danger. For example:
source 'https://rubygems.org'

gem 'danger', '9.2.0'

To install these dependencies, we go to the location of the Gemfileand execute: bundle install. To confirm the installation, we see that a Gemfile.lock file has been generated which contains the exact gems and versions that Bundle has installed.

Then we continue with:

  1. Creating a Dangerfile: This file contains the rules that Danger will use to review the code. Here, you can define code style rules, requirements for pull request reviews, and other automated controls. This file would also be located in the .github folder. It’s nothing more than a Ruby file in which we define what we want to check.
def warn_lint
system("flutter analyze --no-pub --no-fatal-infos --no-fatal-warnings > flutter_analyze_report.txt")

flutter_lint.report_path = "flutter_analyze_report.txt"
flutter_lint.lint(inline_mode: false)
end

def warn_code_style
# check_format.sh runs "dart format" over our codebase
result = system("bash scripts/check_format.sh")
is_code_style_good = result == true

link = "<<Code Style Doc. Link>>"

if is_code_style_good
code_style = "The code style does follow [Code Style](#{link})"

messages = [
"#{code_style}, Great!, a brilliant PR!",
]

message messages.sample
else
code_style = "Your code style **does not** follow [Code Style](#{link})"

messages = [
"#{code_style}, it is possible that someone will not sleep fixing a bug you introduced in this PR, check the format.",
]

fail messages.sample
end
end

begin
warn_lint
rescue => e
warn "Something bad has happened while running the linter checker :( #{e}"
end

begin
warn_code_style
rescue => e
warn "Something bad happened while running the code style checker :( #{e}"
end

Here we use the warn_lint command with the danger-flutter_lint package available on GitHub, which we must add to the Gemfile.

source 'https://rubygems.org'

gem 'danger', '9.2.0'
gem 'danger-flutter_lint'

How to integrate it into CI?

Up to now, we only have configurations that “do nothing” yet, but they will be called from a GitHub Actions flow as follows:

  1. We need to create a workflow file called danger.yml. This file is placed in the .github/workflows folder and will be the basis for the Danger executions in CI.
  2. Define the workflow in the YML file: In the danger.yml file, we define a workflow that installs Ruby, the necessary gems, and runs Danger. Here is a basic example of what it might look like:
name: Run Danger

on: [pull_request]

jobs:
run-danger:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
working-directory: .github/
ruby-version: '3.3.0'
bundler: '2.5.4'

- name: Install dependencies
run: |
bundle config path vendor/bundle
bundle install --jobs 4 --retry 3

- name: Run Danger
run: bundle exec danger

The previous code means:

  • on: [pull_request]: This signifies that the workflow will be triggered every time a pull request is created in the repository.
  • runs-on: ubuntu-latest: Specifies that the job will run on an Ubuntu runner.
  • steps: Defines the steps that will be carried out as part of this job.
  • Checkout code: Uses the action actions/checkout@v3 to clone our repository.
  • Set up Ruby: Uses the action ruby/setup-ruby@v1 to set up the specified Ruby environment.
  • Install dependencies: Installs the necessary gems using Bundler.
  • Run Danger: Executes Danger with bundle exec danger.

We can also find available as open source some jobs that package many of these tasks, for example, we can replace the Install Dependenciesand Run Danger jobs with the job danger-action@v5:

name: Check Lint and Code Style
uses: MeilCli/danger-action@v5
with:
plugins_file: '.github/Gemfile'
install_path: 'vendor/bundle'
danger_file: '.github/DangerfileStyle'
danger_id: 'danger-style'
danger_version: 9.2.0
env:
DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}

In this scenario, this job uses the GITHUB_TOKEN, which is auto-generated and which we access directly with secrets.GITHUB_TOKEN, allowing it to execute all the required commands. For this, we must have the token permissions configured in the repository settings under Actions > General.

Now, all that’s left is to commit and push our code:

git add .github/workflows/danger.yml
git commit -m "Add Danger GitHub Actions workflow"
git push

And so, once we have pushed the GitHub Actions configuration to the repository, we should be able to see it automatically execute on pull requests. This can be verified in the “Actions” tab of the repository on GitHub.

We have thus seen how in a simple and quick way, we configured a Danger System for our Flutter codebase and integrated it with our CI, particularly GitHub Actions.

What do you think? Were you familiar with this?

--

--

Carlos Daniel

Android & Flutter Developer. GDE for Android & Mobile Engineer.