Flutter wrapper — bind your project to an explicit flutter release

If you tried Flutter — starting with the Flutter install process — you have to

  1. clone the Flutter repository and then
  2. manually added flutter to your PATH.

Unconventional, isn’t it?

Coming from Android I’m used to use the gradle-wrapper, a single executable named gradlew, sitting in the root of my project, automatically downloading all dependencies with the correct version. ./gradlew build works for me, my coworkers as well as on my CI server of choice.

Is this also possible for Flutter? Yes, I wrote a flutter-wrapper and it solves many problems once you start working on a production app with a larger team.

Don’t share the Flutter SDK across projects

Having a single Flutter SDK on you system, shared across all projects, isn’t practical. After updating Flutter you also have to update your apps or they’ll become incompatible with the latest Flutter release over time. If you’re working on multiple apps, this becomes pretty time consuming.

Not updating the Flutter SDK is no alternative. Who doesn’t want to use the cool new Material Design Widgets or latest performance improvements?

A Flutter SDK per project is the alternative. That way you’re able to update Flutter for each project at the right time without risking to break other projects.

Missing Flutter SDK pinning

By default, Flutter projects don’t declare a Flutter SDK version which should be used to build the app. Coworkers aren’t able to determinate which version should be used without asking. There’s no hint in any file in the git repository. This makes it impossible to reproduce bugs because builds aren’t deterministic across machines.

Dependencies of Flutter projects are defined in the pubspec.yaml. Interestingly the flutter package doesn’t define a version. Instead the flutter dependency should be retrieved from an sdk, also called flutter.

dependencies:
flutter:
sdk:
flutter

sdk is a special dependency source, explicitly added for flutter. It delegates to a simple implementation called FlutterSdk and resolves packages locally from inside the Flutter SDK which was used to build the project.

Not only doesn’t the flutter package define a version, the flutter package doesn’t have a version! The source code is whatever the SDK currently provides. This is possible because versions can be omitted for local packages, those which haven’t been published to pub.

What’s missing is the exact version of the Flutter SDK, providing the local packages, which have to be used to reproduce a previous build. We can fix that by using pubs Flutter SDK constraints which restrict the version of the Flutter SDK for that project.

dependencies:
flutter:
sdk:
flutter
environment:
flutter: "0.4.5-pre.57"

You can get your current Flutter SDK version by calling flutter --version.

Now, the exact version is in the repository and prevents builds with any other version.

Problem solved? No, we’re only halfway there. Preventing builds with the wrong version is great but pub can’t help you to get the version you’re looking for. It’s hard to find which commit equals version 0.4.5-pre.57.

flutter-wrapper for the rescue

The flutter-wrapper is an executable in your project root called flutterw. When you execute ./flutterw run, it will checkout the correct commit of the Flutter SDK and then run your app.

The gradle-wrapper was a great template to start with. Flutters unusual shape of just being a git repository came in handy this time. Instead of manually managing a configuration file or offline caching, like the gradle-wrapper does, I was able to add flutter as a git-submodule, which straight away solves all the hard work.

Git submodules have a pretty bad reputation and everyone who worked with them knows this. Especially removing them from a project is a real pain in the a$#! That’s why I invested most of the time in a smooth install and uninstall script.

Even without my flutter-wrapper, git submodule is the recommended way to pin Flutter to your project. The wrapper script just makes it less painful.

Although I earlier showed a way to pin the Flutter SDK to a specific version, it is not required when using the wrapper. Pinning with a submodule is easier and more flexible when it comes to updating.

Installing flutterw

Disclaimer: Only tested on OSX.

Installation is done in 30s (depending on your internet connection). Go to the root of your Flutter project and execute this one-liner to run the install script:

curl -sL https://raw.githubusercontent.com/passsy/flutter_wrapper/master/install.sh | bash -

A few seconds later…

Flutter Wrapper installed, initialized with channel master.
Run your app with:     ./flutterw run
Switch channel:        ./flutterw channel beta

This will add the Flutter submodule to your project in directory .flutter. From now on you should use ./flutterw like you would normally use flutter.

Information about updating and uninstalling can be found in the README as well as instructions how you can create new projects by starting with the wrapper.

Flutter settings in IntelliJ

One manual step is required if you’re using Android Studio or IntelliJ: Go to Preferences and verifyFlutter SDK path points to .flutter in your project. It might still point to your global Flutter SDK. You only have to do this once per project.

Benefits from having one SDK per project

Since I moved the Flutter SDK inside my projects, search became much easier. I can simply search my project folder and find all possible callers from my code, from the exact Flutter SDK I’m currently using.

This goes even further: One not so obvious benefit of having Flutter as a submodule is the ability to change source code inside of the SDK. It’s just a local copy, not a precompiled binary. Although I don’t recommend working on a fork, it is easily possible. And it’s so easy to then push changes back to the main Flutter repository after you added tests to verify your change. Contributions are always welcome!

I’ve never felt so close to any other third party project. Now, Flutter is part of my project and I’m not scared of touching or tweaking it. It feels natural.