flutter_config: Different environments when build apk in release mode is null

I have set that:

project.ext.envConfigFiles = [
    debug: ".env.development",
    release: ".env.production",
]

But when run flutter build apk, FlutterConfig.get('DUMMY') always return null.

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 8
  • Comments: 15 (5 by maintainers)

Commits related to this issue

Most upvoted comments

I think I found the solution. I had the problem when i get null on any FlutterConfig.get('DUMMY') with a release build using flutter build apk --release or flutter build appbundle --release (debug mode works OK and returns config values correctly).

After digging into flutter_config code, I found a line in FlutterConfigPlugin.kt:

val clazz = Class.forName("$className.BuildConfig")

In other words - reflection is used to retrieve BuildConfig class for your application which has all your env variables. However, when we use --release flag with flutter command - an R8 obfuscator kicks in and changes class name BuildConfig to something else - thus this line of code results in ClassNotFoundException and provides no env vars.

SOLUTION

To prevent obfuscation of BuildConfig, add R8 obfuscator rule to keep class intact:

  1. Add file android/app/proguard-rules.pro to your app’s project.
  2. Add this line to created file:
-keep class com.yourcompany.app.BuildConfig { *; }

where replace com.yourcompany.app with your app’s package name.

This solved the problem for me - release build now returns env vars as expected. I did not use any flavors. I think we can add this information to documentation

@stpch Yes it should work without flavors. I haven’t had much time to debug this issue. I’ll look into the in the coming days.

It would be great if someone could help out 😃

Shouldn’t it work without flavors? https://github.com/luggit/react-native-config (which this project seems inspired by) works like this, where flavors are completely optional.

I’d imagine most people start out without flavors and add them later in development if needed.

thank you @martynasadomaitis - can we get this added to docs - from what I can see it still isn’t listed

@martynasadomaitis you saved my day! and probably my startup! Thanks!

Found the solution, add -keep class com.YourPackageName.BuildConfig { *; } to android/app/proguard-rules.pro

I’m using flavors and this is the error I’m getting:

Reading env from: .env
**************************
*** Missing .env file ****
**************************

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:processDevelopmentDebugResources'.
> A failure occurred while executing com.android.build.gradle.internal.tasks.Workers$ActionFacade
   > Android resource linking failed
     C:\Users\Home\App\Projects\lask\android\app\src\main\AndroidManifest.xml:35:5-36:54: AAPT: error: resource string/GOOGLE_MAPS_API_KEY (aka com.example.laskdev:string/GOOGLE_MAPS_API_KEY) not found.

EDIT 1

This is my gradle.build:

def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
    localPropertiesFile.withReader('UTF-8') { reader ->
        localProperties.load(reader)
    }
}

def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
    throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}

def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
    flutterVersionCode = '1'
}

def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
    flutterVersionName = '1.0'
}

// Adding env variables for each environment (Production & Development)
project.ext.envConfigFiles = [
        debug: ".env.development",
        release: ".env.production",
]

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
apply from: project(':flutter_config').projectDir.getPath() + "/dotenv.gradle"

def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
    keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}

android {

    flavorDimensions "flavor-type"

    productFlavors {
        production {
            dimension "flavor-type"
            applicationId "com.example.lask"
            resValue "string", "app_name", "Lask"
        }
        development {
            dimension "flavor-type"
            applicationId "com.example.laskdev"
            resValue "string", "app_name", "Lask Development"
        }
    }


    compileSdkVersion 30

    sourceSets {
        main.java.srcDirs += 'src/main/kotlin'
    }

    lintOptions {
        disable 'InvalidPackage'
    }

    defaultConfig {
        applicationId "com.example.lask"
        minSdkVersion 21
        targetSdkVersion 30
        versionCode flutterVersionCode.toInteger()
        versionName flutterVersionName
        resValue "string", "build_config_package", "com.example.laskdev"
    }

    signingConfigs {
        release {
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
            storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
            storePassword keystoreProperties['storePassword']
        }
    }

    buildTypes {
        release {
            signingConfig signingConfigs.release
            useProguard true
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }

        debug {
            signingConfig signingConfigs.debug
            useProguard true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

flutter {
    source '../..'
}

apply plugin: 'com.android.application'

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}

EDIT 2 (Solution)

I found the issue.

I didn’t know we have to declare different env config names in gradle.

In short, I have 2 flavors (production and development) and I need the following:

From this:

...
  // Adding env variables for each environment (Production & Development)
  project.ext.envConfigFiles = [
        debug: ".env.development",
        release: ".env.production",
  ]
...

To this:

...
  // Adding env variables for each environment (Production & Development)
  project.ext.envConfigFiles = [
        developmentdebug: ".env.development",
        developmentrelease: ".env.development",
        productiondebug: ".env.production",
        productionrelease: ".env.production",
  ]
...

@byneapp, please add this information to the Documentation.