DEV Community

iDeskangel
iDeskangel

Posted on

How Flutter does conditional compilation

When we are using C/C++, if there are some codes needn't be compiled, all we have to do is add some conditional compilation codes ( #ifdef #endif), but for Java, Kotlin, Dart, it is hard to achieve that - not it can't be done, but the compiler doesn't support the "conditional compilation" features and in order to achieve the same results, we need do it in other ways.

The reason

It started when I wanted to distribute DaRemote to Huawei app store. After connecting to HMS Core, I found that there are some extra permissions. For example, HMS Core Sdk requires permission to install apps, and the app distributed to Huawei app store does not need Google play settlement service permission.

Therefore, it is desirable to exclude some codes and SDKs and related packages from the compilation environment according to different conditions.

And to achieve this, it needs to be done in two parts: one for Android native side and one for Flutter/Dart side.

Java & Kotlin implementation

Configure build.gradle file

The native side for Android is compiled using gradle, so the setup of "conditional compilation" is also done through the configuration of build.gradle file.

Gradle supports setting different flavors to achieve different compilation configurations. Previously, I just used it to modify some parameters, such as AppId, version number, etc. Now we want to import different SDK dependencies and specify different code through it.

First, two different flavors were created.


    flavorDimensions "flavor_iap"
    productFlavors {
        google {
            dimension "flavor_iap"
            signingConfig signingConfigs.releaseGoogle
        }

        huawei {
            dimension "favor_iap"
            signingConfig signingConfigs.releaseHuawei

Enter fullscreen mode Exit fullscreen mode

Then configure different sourceSets for them.


    sourceSets {
        google {
            kotlin.srcDirs = ["src/google"]
        }
        huawei {
            kotlin.srcDirs = ["src/huawei"]
        }
    }

Enter fullscreen mode Exit fullscreen mode

Finally, associate the dependency with the flavor, i.e. prefix implementation with the name of the flavor and use the lower camel case.

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    huaweiImplementation 'com.huawei.hms:appservice:5.0.4.302'
}
Enter fullscreen mode Exit fullscreen mode

Configure source code

The sourceSets above configures different code sets, and these specified code sets will be merged with the code under src/main - Note that it is a merge and not a replacement.

  1. The first step is to move the code that would conflict under the original /src/main to its respective flavor directory.

Since there is only MainActivity.kt file in the directory (src/main/kotlin/com/... /...) in my Flutter project, just leave the folder empty after moving the file away.

Also create google/kotlin/com/... /... and huawei/kotlin/com/... /... directories under src path, with the same structure as the original src/main.

  1. The second step is to create your own MainActivity.kt file in each of the two directories, and then just implement your own functionality depending on the demands.

Flutter configuration

The main principle of the implementation under Flutter is that it can specify different entry files with the following compilation parameters.

flutter build apk --help
....
-t, --target=<path> The main entry-point file of the application, as run on the
                                    device.
                                    If the "--target" option is omitted, but a file name is provided
                                    If the "--target" option is omitted, but a file name is provided on the command line, then that is used instead.
                                    (defaults to "lib/main.dart")
Enter fullscreen mode Exit fullscreen mode

In addition, it also supports specifying flavors that can be passed through to gradle:

--flavor Build a custom app flavor as defined by platform-specific build
                                    setup.
Enter fullscreen mode Exit fullscreen mode

Since it supports different entry files, the code needs to do some processing to separate the main.dart file.

Code structure modification

I renamed the original main.dart to daremote.dart (the name of the main widget), removed the main function, and created a main.google.dart file and a main.huawei.dart file in the lib directory.

lib
├── daremote.dart
├── main.google.dart
├── main.huawei.dart
Enter fullscreen mode Exit fullscreen mode

compile script

Since there is no good way to "conditionally control" the packages listed in pubspec.yaml, for example, there is no way to create an entry like google_dependencies, so the only way to control it is with the command flutter pub remove:

flutter pub remove --offline in_app_purchase
flutter build appbundle --release --flavor=huawei -t lib/main.huawei.dart
flutter pub add --offline in_app_purchase
Enter fullscreen mode Exit fullscreen mode

To compile for huawei, remove google's in_app_purchase package first, then in the flutter build command, specify the flavor via --flavor parameter, specify the main file via -t, and add the package back after the compilation is completed.

vscode configuration

In development, the project needs to be compiled and debugged, in vscode, we can set up vscode's launch.json. The important thing to note is to configure the program field to specify the entry file:

"configurations": [
    {
        "name": "huawei",
        "request": "launch",
        "type": "dart",
        "program": "lib/main.huawei.dart",
        "args": [
            "--flavor",
            "huawei",
            "-t",
            "lib/main.huawei.dart"
        ]
    },
    {
        "name": "google",
        "request": "launch",
        "type": "dart",
        "program": "lib/main.google.dart",
        "args": [
            "--flavor",
            "google",
            "-t",
            "lib.main.google.dart"
        ]
    }
]
Enter fullscreen mode Exit fullscreen mode

Finally

For interpreted languages, it's fine, but for compiled languages, it's really inconvenient not to have "conditional compilation". In order to achieve the same purpose, a lot of extra work has to be done, most notably in a piecemeal fashion.

At one point, I had thought to control all processes by bash scripts, but finally took some time to study how to achieve the purpose by using the features provided by the framework itself.

Latest comments (2)

Collapse
 
pablonax profile image
Pablo Discobar

Helpful article! Thanks! If you are interested in this, you can also look at my article about Flutter templates. I made it easier for you and compared the free and paid Flutter templates. I'm sure you'll find something useful there, too. - dev.to/pablonax/free-vs-paid-flutt...

Collapse
 
mma profile image
mma_apps • Edited

Seriously agree! When I use Swift for iOS development in Xcode, I often use conditional compilation. It is very useful and important to me. But when I was learning Android Stuidio, I found that neither Java nor Kotlin had this feature. It made me crazy. Later, I gave up the idea of developing an Android version of the app.