Excluding Dart code from the release compiled executable

Antonello Galipò
Mar 31, 2020 · 4 min read
source: https://pixabay.com/illustrations/board-circuits-control-center-trace-911636/

Every big project can have its flavors, its customizations, for different cases, clients, running environments, etc. Whenever we want to tweak some features, themes, graphics, we are pushed to evaluate conditions, no matter how elegant they can be. But what happens when we exclude those features in our Dart program? This question came to me when, due to security reasons for the Flutter app we are developing at work, the following question was asked to me: will development environments URLs be compiled anyway in the executable, even if they are not used?

If Dart had something like C/C++ preprocessor directives, I would have said a straight “yeah, sure”.

But no, Dart hasn’t stuff like that, nope. But it has something cool, which is the so called Tree Shaking, which can be described as the process of eliminating code that will never be executed, resulting in lighter executables.

Tree shaking in action. Source: https://blog.mgechev.com/images/ng2-build/tree-shaking.gif

So we know that Dart uses Tree Shaking to eliminate unused code. But we use it. Sometimes. Sometimes not. This means that if sometimes we are told to use it we will, some times we don’t. As far as the Dart analyzer is concerned, we always might use that code.

At first, we might think of something like this:

This will exclude the value feature2 from the list and will execute the statement at line 14, but the everything will be compiled anyway.

We can have evidence of this by compiling this code using dart2native and opening its output with some Hex editor, I’m using HexFiend on MacOS:

Or if we don’t feel like using an editor just for this, we can use the good old grep command:

This is because final has a meaning of single assignment, henceenable will be assigned only once, and will never changed again. But it all happens at runtime, and for this reason the compiler won’t exclude the code.

But we can do this instead:

We are now using the const keyword. This will make the Dart compiler sure that our dearly featureIsEnabled is false and always will be. This is not a one time assignment, this is something carved in the stone, that will go under the process of canonicalization.

Basically, when something is canonicalized the compiler says “well, this thing is this. It won’t be otherwise. No need to determine it at runtime” this is why using const for variables and especially constructors is a good optimization, they are “executed” at compile time and the value will be ready without undergoing to the whole construction process.

The behavior of the code above is granted at compile time, and this means that feature2 will never be added to the list and line 12 will never be executed. And if it’s not executed, the Tree Shaking will cause that statement to be excluded from compilation, resulting in an effective exclusion of code, which is what we were looking for in the first place.

Using grep again, we won’t get any matches this time:

Note how this behavior applies to many cases in which conditions are used, collection if and ternary operators as well!

You can check here if you want know more about how Tree Shaking behaves in a Flutter application. You can always do some tests decompiling the app (like I did since I was excluding values and not methods, and wanted to double check).

Regarding the final vs const concept you can, instead, read more here.

Last, but not least, here’s my original question on StackOverflow which helped me in the process of doing this and writing this article.

Hope you enjoyed this article and that it might have been of help for people who had similar needs.

Greetings!

Antonello

Flutter Community

Articles and Stories from the Flutter Community