Size Matters!? Shaving Size of Your Flutter App

Probably APK (or app bundle) and IPA size is not something that is causing you sleepless nights. At least it isn’t for me.

We know that there are some tradeoffs when going cross-platform route. Most of people will name performance or look-and-feel as main concerns. Maybe some will mention the deliverable size. This is what I will tackle in this blog post. The size of your app for the end user.

Of course, going the cross-platform route we accept that our deliverable would be bigger. The main reason is that platform does not provide the runtime for our out app. We always need to ship everything to start and run our app.

Why should you care about your download size? In most cases there are two, three, or thirty other apps that will do exactly the same thing as yours. If your potential user need to wait one more minute to install yours app, this is potential one minute when he/she can give a try for a competitor app. This is also more time when something may go wrong, more bytes to transfer means higher probably that something will go wrong with the internet connection etc.

Also smaller app size may be a factor for the Play/AppStore listing. As Apple/Google needs to push all of that data to the user and they need to pay for the upload 😀 (just kidding ;)). But anyway, at least Play Console shows how your APK size compares to the app category median.

Some basic things you can find in this blog post, I will cover all the rest ;). Would recommend you go over this blog first, configure progurad etc. and get back here for more!

R8

This is Android specific. It is a successor to proguard, that can also remove not used resources. You can enable it by adding android.enableR8=true to android/gradle.properties (although it may be already enabled). One thing to note is that it may remove not used resources, like eg. your notification icon, please follow instructions from flutter_local_notifications in order to keep your notification icon.

Fonts

If your app uses custom fonts, I would recommend finding alternatives in Google Fonts, and then adding google_fonts to your dependencies. You may wonder how additional dependency may reduce your APK size? Well, first of all you may remove all other fonts from your project. Secondly google_fonts will dynamically download font when it is used and it will download it in a specific style and language, so that you don’t need to ship all of the variation with your project. Yes, this is another tradeoff, most probably fonts will be downloaded on app initial start… but it is less bytes to push when app is installed.

Icons

You probably use a few icons either from Icons or CupertinoIcons class. Normally this means that all of the icons are embedded into your Flutter bundle. But since flutter 1.17, you can add --tree-shake-icons option to flutter build command, that would remove all of the not used icons from the bundle. Few more bytes shaved!

Split Debug Info

You probably don’t want to include Dart debug information in your production release. --split-debug-info is another option you can add to flutter build command to store debug information separately. This option takes additional argument that is a directory where debug information should be stored.

In case of my HabitChallenge app, I have created a git submodule, where I automatically store, and tag, debug information after the release.

Then when you need to add debug information to your stacktrace, you can use flutter symbolize command and point to appropriate version of debug-info.

Putting All Together

In my case, I have created bash scripts that handle the release process (let me know if you are interested to know how I do releases for HabitChallenge).

Android

For android things are fairly easy, since you can manually upload APK(s) or App Bundle to Play Console (or point fastline to do it for you automatically).

appVersion=$(grep 'version: ' pubspec.yaml | awk '{print $2}')

flutter build appbundle \
  --release \
  --tree-shake-icons \
  --shrink \
  --split-debug-info=./debug-info/"$appVersion"/android/ \
  --obfuscate

cd ./debug-info/ || exit 1

git add .
git commit -m "Adding debug-info for $1 version $appVersion"
git push origin

What is going on here? First of all I’m getting current app version from pubspec.yaml. Then I am running flutter build command with all of the size optimization flags. The debug-info output is stored in a path that contains app version and platform. Finally I am going into debug-info directory and committing and pushing all the changes.

iOS

The thins are a bit trickier here. Since we need to use xcode with its own build system to deploy our app to AppStore.

Start the xcode, open your Flutter app, then select Runner root node, then Runner target, next Build Phases, and finally expand Run Script node. Here we need to do some changes…

No worries, I won’t force you to retype all this in, here is a plain text version of the release script:

if [ "${CONFIGURATION}" == "Release" ] ; then
  echo "Enable release optimizations"
  appVersion=$(grep 'version: ' ../pubspec.yaml | awk '{print $2}')
  export TREE_SHAKE_ICONS="YES"
  export DART_OBFUSCATION="YES"
  export SPLIT_DEBUG_INFO="./debug-info/${appVersion}/ios/"
fi
/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh"

This time, we first discover if build is run in Release mode, then again we get the app version from pubspec.yaml to store debug information in appropriate directory. Then we export environment variables that are read by the build script. And finally run the Flutter’s xcode build script!

Summary

How much can you shave? Well that depends on how much fonts and icons your app is using 😉 Regarding the debug-info, in case of HabitChallenge it saved about 900KB (from 26.3 to 25.4MB) of App Bundle. According to Play Console app downloadable size is from 9.50 to 10.3MB.

Do you think it is worth it? Will you enable those optimizations in your build?

4 Comments Size Matters!? Shaving Size of Your Flutter App

    1. Dariusz Łuksza

      I would say that removing debug info isn’t something that would complicate your life on daily basis, not even weekly or monthly. Of course this depends how “exceptional” your app is. In case of HabitChallenge I haven’t yet used debug-info, so no life complication for me 😉

      Reply
    1. Dariusz Łuksza

      Maybe, when majority of the apps are Flutter based, the Play store will be able to do this optimization on its own. But for now you need to do it manually

      Also, remember that if its done automatically by the Store it self, when you use third party crash detection services (like Crashlytices) you’d probably need to manually download obfuscation data and upload it back to the service provider you’re using.

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *