If you tried Flutter — starting with the Flutter install process — you have to
- clone the Flutter repository and then
- manually added flutter to your
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.
flutter_wrapper - Flutter execution wrapper which keeps the flutter version in sync for each project
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
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
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.
You can get your current Flutter SDK version by calling
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
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.
flutter_wrapper — Flutter execution wrapper which keeps the flutter version in sync for each project
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.
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 runSwitch 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.
One manual step is required if you’re using Android Studio or IntelliJ: Go to
Preferences and verify
Flutter 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.
If you have questions or feedback, let me know! Hit me on Twitter for a quick chat. I’m super interested!