Skip to content

Environments

Dustin Catap edited this page Jun 19, 2024 · 2 revisions

Multiple Environments

Each project should have the following environments:

  • Development is used by the developers. Testing should not be done in this environment since it is always changing.
  • Test will be used by your testers to ensure that the software is error free.
  • UAT ensures that your software is working as expected in the hands of your intended users.
  • Production contains the latest version of your software that passed all testing and is generally available to your intended user.

Setup and Debugging an Environment

Under .secrets folder, we can find the different variables used for each environments:

image.png

We use Command Variable extension in order to select which environment to run locally. Pressing F5 will show a picker before debugging the application:

image.png

Read more about this setup in this article and here.

Secrets File

A specific .json file per environment is passed to Flutter's build arguments as Dart defines. Checkout our .vscode/launch.json file to see how we pass the environment file to the build arguments.

Below are sample values of a secret file for the development environment:

{
    "appEnvironment": "dev",
    "appServerUrl": "https://jsonplaceholder.typicode.com/",
    "appId": "com.mycompany.starterkit.app",
    "appIdSuffix": ".dev",
    "appName": "Starterkit App (Dev)",
    "iOSDevelopmentTeam": "XXXXXXXXXX",
    "iOSDevelopmentProfile": "Your Development Profile Name",
    "iOSDistributionProfile": "Your Distribution Profile Name",
    "iOSExportMethod": "ad-hoc"
}

IMPORTANT

  • The secrets file should be kept in a secure location that is not accessible to the public. And should be given only to the developers who need it. For CI purposes, the secrets files should be stored in a secure location in the CI server (like GitHub Secrets) and should be re-created for each build depending on the environment.
  • The secrets file are added to this repository by default. Be sure to remove them on yours once you updated the values.
  • For iOS, you need to add the necessary provisioning profiles and signing certificates to your local machine. You also need to supply the correct iOSDevelopmentTeam value in the secrets file and in the DartDefine-Defaults.xcconfig file.
  • The secrets file are also used in Fastlane. See Fastfile for more details.

Accessing Dart Defines in the Platforms

The Android and iOS native projects are already configured to update their current configurations based on the environment file we will use. For example, the provisioning profile used for running the app on an iPhone device are automatically selected (given that the profile is available in your local machine).

You can access other Dart defines in the native projects by referencing the key directly.

An example of this can be found in iOS' Info.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    ...
	<key>CFBundleName</key>
	<string>$(appName)</string>
    ...
</dict>
</plist>

and in Android's build.gradle:

def dartDefines = [:]
if (project.hasProperty('dart-defines')) {
    // Decode dart-defines, which are comma-separated and encoded in Base64, and store them in a variable.
    dartDefines = dartDefines + project.property('dart-defines')
            .split(',')
            .collectEntries { entry ->
                def pair = new String(entry.decodeBase64(), 'UTF-8').split('=')
                [(pair.first()): pair.last()]
            }
}

def appIdSuffix = dartDefines.appIdSuffix != 'appIdSuffix' ? dartDefines.appIdSuffix : ''
def appIdComplete = "${dartDefines.appId}${appIdSuffix}" 
println "Building: ${dartDefines.appName} (${appIdComplete}) for ${dartDefines.appEnvironment} environment"

android {
    ...

    defaultConfig {
        println "Building $appIdComplete"

        applicationId $appIdComplete
        if (appIdSuffix?.trim()) {
            applicationIdSuffix appIdSuffix
        }
        minSdkVersion 31
        targetSdkVersion 34
        versionCode flutterVersionCode.toInteger()
        versionName flutterVersionName

        resValue "string", "app_name", "${dartDefines.appName}"
    }
}