Automate Flutter package upgrades with GitHub Actions(a Dependabot alternative)
Tags: flutter, github, dependabot, actions
Published: 2021-07-05 03:16:06

Like with pretty much any other framework, keeping up with package upgrades in Flutter can get pretty tedious. I decided to see if I could make things a little less tedious over the weekend and I'd like to share a GitHub Action workflow that I came up with while experimenting.

## What about Dependabot?

I'd actually be happy to use Dependabot but there isn't full support yet. Somebody is [working on it](https://github.com/dependabot/dependabot-core/pull/3438) at the moment though. I'm confident there will be full support one day, it's just a matter of time. For now though Dependabot isn't really a realistic option.

## Using GitHub Actions

There was the option of creating an Action and putting it on the Actions Marketplace but I'm not entirely sure what that would involve yet and I'm not that committed to the idea yet either considering that there is work being done on the Dependabot front. So in the end I just decided to create a workflow with existing Marketplace Actions. Here's roughly the set of steps I wanted the workflow to use:

1. Check out the repo
2. Pull in cache data(so the next step runs a little faster)
3. Setup Flutter CLI so it can be used in the next steps
4. Run `flutter pub upgrade --major-versions`
5. Run `flutter pub get`
6. Run `flutter test`
7. (Optional) Capture the output of `flutter pub outdated` for later
8. If there are changes from (4) commit them to a new branch and open a PR

And obviously I'd want this workflow to run every so often so it would need to be scheduled with cron. In the end this is what I came up with:

```yaml
name: Upgrade packages

on:
  workflow_dispatch:
  schedule:
    - cron: "0 0 * * *"

env:
  flutter_version: "2.2.2"

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  upgrade_packages:
    # The type of runner that the job will run on
    runs-on: ubuntu-latest

    # Steps represent a sequence of tasks that will be executed as part of the job
    steps:
      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      - uses: actions/checkout@v2
      - name: Cache Flutter dependencies
        uses: actions/cache@v2
        with:
          path: /opt/hostedtoolcache/flutter
          key: ${{ runner.OS }}-flutter-install-cache-${{ env.flutter_version }}  
      # Installs Flutter    
      - uses: subosito/flutter-action@v1
        with:
          flutter-version: ${{ env.flutter_version }}
          channel: stable   
      # Upgrade packages(bump major versions)    
      - name: Run upgrade
        run: flutter pub upgrade --major-versions
      # Get/install packages  
      - name: Run get
        run: flutter pub get  
      # Run tests  
      - name: Run tests
        run: flutter test
      # Captures the output of `flutter pub outdated`  
      - name: Check remaining outdated packages
        id: check_outdated
        run: |
          content="$(flutter pub outdated)"
          content="${content//'%'/'%25'}"
          content="${content//$'\n'/'%0A'}"
          content="${content//$'\r'/'%0D'}"
          echo "::set-output name=outdated_info::$content"
      # Opens a pull request with changes    
      - name: Create Pull Request
        uses: peter-evans/create-pull-request@v3
        with:
            token: ${{ secrets.PAT }}
            commit-message: Upgrade packages
            title: Upgrade packages
            body: | 
              Output for outdated check following upgrades:
              ${{ steps.check_outdated.outputs.outdated_info }}
            branch: feature/update-packages
```

For this to work you need to generate a `Personal Access Token` in your GitHub account's settings and then add that as a secret to your repository. You can learn more about this [here](https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token). This is what the `${{ secrets.PAT }}` value represents in the last step.

You might have also noticed that I have this set to run everyday at 12am UTC. I'm going to tweak this later on but like I said I'm just experimenting with things at the moment and want to see if I run into any issues down the road. If I find some improvements I can make I will update this post. If you wanted to run this on a weekly schedule instead, you could use something like "0 0 * * Sun" instead(12am UTC every Sunday).

## Caveats

The above workflow has been working pretty good for me for the last few days on some simple, small projects. That being said it is far from perfect. 

First of all the above workflow won't upgrade your Pods on the iOS side of things. If you switch from `ubuntu-latest` to `macos-latest` you could run `pod install --repo-update` in the ios directory of your project, I did try this out personally and it seemed to work okay - but keep in mind that workflows that use MacOS cost [something like 10 times more than workflows that use Ubuntu](https://pmagentur.com/wissen/technologie/github-actions-pricing/). This would probably matter less if you were only running the workflow once a week or something like that.

Another thing you could tweak is the `flutter pub upgrade --major-versions` command. This command will potentially pull in breaking changes because it will upgrade over major versions. You can opt out of this behaviour by just running `flutter pub upgrade` instead. 

One more shortcoming of the workflow above is that it only runs your tests. Your tests might not cover everything so adding a `flutter build ios` or `flutter build apk` might add an extra layer of security if the upgrades break your project. I think I'm going to look into all of these points as I keep tweaking the workflow.

## Summing up

Like I said the workflow I introduced above definitely isn't perfect but I think that it's a good start and something that can be used to keep sane while we all wait for Dependabot support. If you're interested in more Flutter tips, I wrote another post about adding linting to a project a couple of days ago - you can find it [here](https://developermemos.com/posts/linting-flutter-projects).