Saturday, August 26, 2023

Build Flutter app on Linux host

I have been busy developing a Flutter app. When come to CI/CD processes, I still rely on online repository CI/CD pipeline such as BitBucket Pipeline, GitHub Actions or even better all-in-one Flutter built-in functionality such as Codemagic especially for Android APK build. While they already offer complete CI/CD processes, I still think why not to use my own server and deploy a Flutter build system there.

Server requirements

I'm running a DigitalOcean droplet, 4 vCPUs, 8GB RAM running Ubuntu 20.04 LTS 64bit. Since the Flutter Android build process uses a huge amount of resources, especially RAM, I don't think this can be achieved by a system running RAM less than 8GB at least based on what I have tested. With 4GB RAM, after running build for a while, the build processes will be terminated by Linux Out-Of-Memory (OOM) killer or the amount of available RAM is simply not enough.

After server resource requirements above, there are also some build requirements for Linux that listed on Flutter - Linux installation page, of course that includes Git.

Install location

All build components will be located in my home directory inside "dev" directory or ~/dev. The reason I'm doing this because I don't want to mix any of them inside any root sub-directories and to simplify whole build process steps.

Install Flutter

To install Flutter, we can do by either using Snap or manual install, here I choose manual install. When this post is written, Flutter has reached version 3.13. Get the Flutter latest version zip and extract it into ~/dev.

cd ~/dev 
tar xf flutter_linux_3.13.5-stable.tar.xz
mv flutter_linux_3.13.5-stable flutter
rm flutter_linux_3.13.5-stable.tar.xz

Add Flutter path into PATH environment variable. We can do this either in ~/.bashrc (local) or /etc/profile (system-wide). But again, I don't want to make any system change, so I just append these lines at the end of my .bashrc file.

FLUTTER_BIN="/home/ardhan/dev/flutter/bin"
export PATH="$PATH:$FLUTTER_BIN"

Install Android SDK

As we build on a remote host and working on command based tools only, we don't need Android Studio to have all the Android SDK components as described on Flutter getting started page. We just need the all the basic SDK including sdkmanager which is included in command-line-tools package of Android SDK. Head to Android Studio download page, download command line tools for Linux and put it also inside ~/dev directory (latest file is commandlinetools-linux-10406996_latest.zip). We're installing Android SDK in ~/dev/android-sdk.

cd ~/dev
unzip commandlinetools-linux-10406996_latest.zip
mkdir android-sdk
mv android-sdk-cmdline-tools android-sdk/cmdline-tools
rm commandlinetools-linux-10406996_latest.zip

Now to add android-cmdline-tools/bin directory in PATH, do the same thing we added PATH on Flutter above. Add these following lines at end of ~/.bashrc

ANDROID_SDK_CMDLINE_TOOLS_BIN="/home/ardhan/dev/android-sdk/cmdline-tools/bin"
export PATH="$PATH:$FLUTTER_ROOT:$ANDROID_SDK_CMDLINE_TOOLS_BIN"

Then we might need start a new shell or just simply logout and re-login to activate the new PATH. Now, we can get all the Android build tools with sdkmanager command line tool. We set ~/dev/android-sdk directory as our Android SDK build tools location, and run sdkmanager to get list of available packages.

sdkmanager --sdk_root=/home/ardhan/dev/android-sdk --list

There are too many Android build packages and we may wonder which packages and versons to have. But don't worry, sdkmanager will automatically resolve all the package dependencies. We just need to collect informations about the version of our existing build tools, platform sdk, etc. in your local Android project. If you are using Android Studio, go to File | Settings | Appearance & Behavior | System Settings | Android SDK to view packages that are installed, or you can also use sdkmanager tool that should be located in your local Android SDK.

As you can see from the screenshot above, some major installed packages are

  • SDK Platforms: Android 11.0 (R)
  • SDK Tools: Android SDK Build-Tools 34, Android SDK Command-line Tools (latest), Android Emulator

The SDK platform and SDK build tools are the most required packages for build process. We can skip Android SDK Command-line Tools as we already installed it and for Android Emulator, we obviously don't need an emulator in a server-like host. Now, lets run sdkmanager to install the required SDK tools, the --sdk_root option tells sdkmanager where to install the packages, then you might asked to accept some licenses.

sdkmanager --sdk_root=/home/ardhan/dev/android-sdk \
  --install "platforms;android-11"
sdkmanager --sdk_root=/home/ardhan/dev/android-sdk \
  --install "build-tools;34.0.0"

You might asked to accept some licenses, you know what you to do right?. After they have been installed, add ANDROID_SDK_ROOT environment variable in ~/.bashrc.

ANDROID_SDK_ROOT=/home/ardhan/dev/android-sdk
export ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT

Install OpenJDK

And of course we'll need a JDK (Java Development Kit), Android build uses OpenJDK which is the open source implementation of Java. Choosing Java version to work correctly is a bit tricky since specific Gradle (The Android build system) version also depends on a specific JDK version. If you are unsure about both Gradle and JDK version you are currently use, just follow the version of Gradle and Java your local computer. You can do this by executing Gradle's build script inside "android" directory of your Flutter project. These scripts are gradlew for Linux and gradlew.bat for Windows. Anyway, here my Gradle script output under Windows 10.

C:\Projects\app\android>gradlew.bat --version
------------------------------------------------------------
Gradle 7.4
------------------------------------------------------------
Build time: 2022-02-08 09:58:38 UTC
Revision: f0d9291c04b90b59445041eaa75b2ee744162586
Kotlin: 1.5.31
Groovy: 3.0.9
Ant: Apache Ant(TM) version 1.10.11 compiled on July 10 2021
JVM: 11.0.11 (Microsoft 11.0.11+9)
OS: Windows 10 10.0 amd64

That output shows my OpenJDK is version 11 (Microsoft build) and Gradle version is 7.4. Actually you don't need to have the same version of OpenJDL as listed above. Gradle has a Java Compability Matrix table shows compability between various Gradle and Java versions.

My Flutter project uses Gradle 7.4 and according to the compability table above, Gradle 7.3 compatible with Java 17, that means Gradle 7.4 should also work with Java 17. So here I choose OpenJDK version 17. But of course, we can still use OpenJDK 11, I just wanted to play with the newer version. Head to OpenJDK Archive to download it, There I choose OpenJDK 17.0.2 (build 17.0.2+8) as the latest build of version 17, download the Linux 64bit version and put into ~/dev directory and extract.

cd ~/dev
tar xzf openjdk-17.0.2_linux-x64_bin.tar.gz
mv openjdk-17.0.2_linux-x64_bin openjdk-17.0.2

Then again, add this OpenJDK binaries into PATH and create a new JAVA_HOME environment variable, this JAVA_HOME is required for every Java installation. At these new lines into ~/.bashrc.

JAVA_BIN="/home/ardhan/dev/jdk-17.0.2/bin"
JAVA_HOME="/home/ardhan/dev/jdk-17.0.2"
export PATH="$PATH:$FLUTTER_BIN:$ANDROID_SDK_CMDLINE_TOOLS_BIN:$JAVA_BIN"
export JAVA_HOME=$JAVA_HOME

Final .bashrc

So to recap, my final ~/.bashrc shall look like this, remember these new lines were appended at end of the file.

FLUTTER_BIN="/home/ardhan/dev/flutter/bin"
ANDROID_SDK_CMDLINE_TOOLS_BIN="/home/ardhan/dev/android-sdk/cmdline-tools/bin"
ANDROID_SDK_ROOT="/home/ardhan/dev/android-sdk"
JAVA_BIN="/home/ardhan/dev/jdk-17.0.2/bin"
JAVA_HOME="/home/ardhan/dev/jdk-17.0.2"
    
export PATH="$PATH:$FLUTTER_BIN:$ANDROID_SDK_CMDLINE_TOOLS_BIN:$JAVA_BIN"
export ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT
export JAVA_HOME=$JAVA_HOME

Building

To test Flutter Android build, we might need to re-login to shell to apply the new ~/.bashrc. Then head to your Flutter project and issue these flutter commands.

cd ~/my-app
flutter clean
flutter build apk

Flutter might download additional required packages, tools, asked to accept some licenses etc. If something went wrong, check the message for more informations, also consult with flutter doctor command. In the future, I would like to write another post about how this build process can be automated, such as what online build pipelines do, creating a git repository with its githook to trigger automatic build.