Configuring Flutter apps using --dart-define-from-file

Configuring Flutter apps using --dart-define-from-file

Michał Kochmański

Introduction

As a cross-platform solution, Flutter makes our lives more manageable again and improves development. Each new version improves and offers more and more possibilities for developers. The development of Flutter goes hand in hand with the development of the Dart language. 

Starting from version 3.7, Dart introduces several new features that facilitate application configuration management. Among the most significant are:

These functionalities empower developers to manage better environment variables, like API keys, database credentials, sensitive data, and application configuration. These can be particularly useful when building multi-layered applications or tailoring an application to various environments (flavoring) like production, testing, or development.

white labeling

White labeling

The solution presented below can also be great for white-label apps. To explain what a white label app is, from a perspective of CI/CD and what Codemagic’s documentation provides:

According to Codemagic:white labeling” refers to automating the process of rebranding your core app for each customer and then publishing the app to stores or other distribution channels. A white labeling pipeline will run scripts to change colors, logos, images, and fonts and update other app settings such as bundle identifiers, provisioning profiles, certificates, API endpoints, and other configuration settings unique to each customer.

Vue is flexible, easy to learn, and powerful. Its ecosystem is still growing and it already has everything you need to build all kinds of applications (yes, mobile apps, too).
Vue is currently one the most starred projects on GitHub.
As for 2022, it’s the eighth most-starred frontend framework.

From a product (app) perspective, “white labeling” can be treated as configuring the app's branding rather than hardcoding to the app's code. JSON file-based configuration addresses most white labeling issues. This setup is undoubtedly an essential step towards making this process possible.

The most significant advantages of --dart-define-from-file

Let's imagine that we have various secrets and configurations necessary for white labeling scattered throughout our codebase (e.g., resource values in android/app/src/prod/res/values/string.xml), and the developers have to manage it and remember the location of every piece of data. That’d be nearly impossible to keep intact and likely lead to errors as the app grows. Flutter projects often depend on several external dependencies, including backend systems and 3rd-party services configured on the Dart and native side of the codebase.

First, to avoid publishing each flavor's configuration with each app release, we must keep the configuration out of the codebase. It is also quite dangerous because we can accidentally introduce something undesirable into public view. Adding appropriate declarations in .gitignore may not work in such a case. Introducing something undesirable is especially easy for a new developer to join the team and be unaware of the entire project. Furthermore, she or he may have trouble finding something.

Secondly, such a scattering of the important ones makes managing it very difficult. Thanks to --dart-define-from-file and JSON configuration files, we have everything in one place. We can easily and quickly change this data depending on the environment. This is a highly convenient solution for flavoring apps.

Moreover, configuring such JSON files in our Flutter project allows us to access these variables on the native side easily without additional configuration and parsing, which is another great advantage. Flutter, as a cross-platform solution, allows us now to touch the native side here easily without extra effort. Not every Flutter dev has experience with native languages (it's worth having such knowledge :D), so any such facility is valuable.

How does it work for Flutter?

This mechanism lets developers define environment compile-time variables and load configuration data from JSON files. As a result, it becomes possible to tailor the application flexibly to various environments and easily alter its behavior without the need to modify the source code.

Previous solution with --dart-define parameter

Previously, to pass multiple environment variables, we had to use the --dart-define parameter and pass a sequence of these variables.

--dart-define=var1=value1
--dart-define=var2=value2
--dart-define=var3=value3

--dart-define-from-file

We currently use one JSON file for a specific environment, and we have access to all variables simultaneously. This extremely convenient solution can save us time when, for example, we implement various scripts in Makefile.

--dart-define-from-file=keys_staging.json

keys_staging.json

{
...
  "MERCHANT_ID": "merchant_id",
  "TERMS_CONDITIONS_PATH": "/terms",
  "PRIVACY_POLICY_PATH": "/privacy-policy",
...
}


Thanks to this solution, CD becomes much simpler. If, for example, we use VS Code, we can also trivially add the appropriate configuration in the launch.json file.

{
  "configurations": [
    {
      "name": "Debug Prod",
      "type": "dart",
      "request": "launch",
      "program": "lib/main.dart",
      "args": [
        "--flavor",
        "prod",
        "--dart-define-from-file",
        "keys_production.json"
      ]
    },
   {
      "name": "Debug Staging",
      "type": "dart",
      "request": "launch",
      "program": "lib/main.dart",
      "args": [
        "--flavor",
        "staging",
        "--dart-define-from-file",
        "keys_staging.json"
      ]
    },
  ]
}

Accessing environment declarations

When seeking to access specified environment declaration values, it's advisable to employ one of the fromEnvironment constructors within a constant context for optimal efficiency. For boolean values, utilize bool.fromEnvironment; for integers, opt for int.fromEnvironment; and for other types of values, such as strings, rely on String.fromEnvironment. This approach ensures streamlined access and clarity in handling various environment declarations. It's worth remembering that environment declaration constructors only work when called as const. Most compilers need to be able to evaluate their value at compile time.

String get merchantId =>
      const String.fromEnvironment('MERCHANT_ID');

Each fromEnvironment constructor necessitates the inclusion of the name or key corresponding to the environment declaration. Additionally, it accommodates an optional named argument, defaultValue, which enables the override of the default fallback value. This fallback value is employed when the declaration is undefined or the specified value cannot be parsed as the expected type. This comprehensive approach ensures robust handling of environment declarations, providing flexibility and clarity in configuration management.

Understanding the native side

To understand how iOS deals with env variables, we need to cite the definition of xcconfig format files.

A Configuration Settings File (a file with a .xcconfig file extension), also known as a build configuration file or xcconfig file, is a plain text file that defines and overrides the build settings for a particular build configuration of a project or target. This type of file can be edited outside of Xcode and integrates well with source control systems. Build configuration files adhere to specific formatting rules, and produce build warnings if they do not.

The Generated.xcconfig file in a Flutter iOS project is an automatically generated configuration file created during the project compilation for the iOS platform. It's part of building an iOS project in the Flutter environment. Depending on changes in the project or configuration, this file may be regenerated to reflect those changes. You can read more about this topic here.

generated.xcconfig

iOS

When you enter the realm of the project editor's "Build Settings" tab, you're met with an extensive array of configuration options scattered across projects, targets, and configurations. Fortunately, there is a more efficient method for handling this labyrinthine configuration that doesn't involve navigating through a maze of tabs and disclosure arrows. Xcode build configuration files, often referred to by their .xcconfig extension, offer a means to declare and manage build settings for your application independently of Xcode.

In essence, each configuration file comprises a series of key-value assignments following this syntax:

MERCHANT_ID=merchantId

All environmental variables defined in the JSON files are similarly mirrored within this file.

Limitations

Xcconfig files interpret the sequence // as a comment delimiter, irrespective of whether it's enclosed in quotation marks. The easiest thing to do

  • when we want to pass a specific path

  • in our env variable is to simply exclude

  • the scheme and prefix URLs with https:// in our code instead.

Interesting case

Let's assume we need access to our env variable in AppDelegate.swift. We also know that build settings defined by the Xcode project file, xcconfig files, and environment variables are only available at build time. The solution to this problem is to use the Info.plist file. Info.plist file is compiled according to the build settings provided and copied into the resulting app bundle. Therefore, by adding references to specific env variables, e.g. $(MERCHANT_ID), we can access the values for those settings through the infoDictionary property of Foundation’s Bundle API.


Info.plist

<key>MerchantId</key>
<string>$(MERCHANT_ID)</string>
AppDelegate.swift

guard let brazeApiKey = Bundle.main.object(forInfoDictionaryKey: "MerchantId") as? String
else {
   throw NSError(
     domain: "Merchant id error", code: 1,
     userInfo: ["reason": "Problem with getting merchant id"])
}

Android

Let's take a closer look at the Android platform. Flutter 3.7 introduced a fix to pass the env variable set in the JSON file to build.gradle. Great, isn't it? This fix is part of the Flutter build_info.test file in the flutter_tools folder.

Suppose we want to manage the deep link scheme from configuration files. This may be needed when, for example, we create a white app - different brands, so the deep links should be different. The deep link scheme is set in AndroidManifest.xml using the current configuration and proper string resource. To use our env variable, create a new resValue in build.gradle. The resource created this way is not limited to AndroidMainfest.

AndroidMainfest.xml

 <intent-filter
    android:autoVerify="true">
    <action
       android:name="android.intent.action.VIEW"/>
    <category
       android:name="android.intent.category.DEFAULT"/>
    <category
       android:name="android.intent.category.BROWSABLE"/>
    <data
       android:scheme="@string/deeplink_scheme"
       android:host="@string/deeplink_host"/>
​​ </intent-filter>
android/app/build.gradle

defaultConfig {
...
resValue "string", "deeplinkScheme", DEEPLINK_SCHEME
...
}
android/app/src/prod/res/values/string.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
...
    <string name="deeplink_scheme">@string/deeplinkScheme</string>
    <string name="deeplink_host">@string/deeplinkHost</string>
...
</resources>

The resValue value from the build.gradle file is passed to the Android application as a resource at compile time. Gradle processes these values during compilation and includes them in the Android app's resource files (such as strings.xml).

test heading h4

content for test heading 4

Conclusion

--dart-define-from-file parameter and enhancements in JSON file handling in Flutter 3.7 bring new possibilities for developers in managing application configuration. These features make it easier to customize an application for different environments while maintaining code transparency and flexibility. Users can leverage them when building Flutter applications to increase scalability and ease of configuration.

Please note that there are other ways to deal with the problem of configuring our app. There are other ways to use the environment variables mechanism: flutter_dotenv, envied. However, this article focuses on solving this problem using JSON files, because this method works great when we have several flavors and want to manage our app from one place better. If someone is planning to create what can be called a white app, this method will certainly help you deal with the basic problems. We have greater control over all data, making working in a larger team easier. Automation becomes easier with this approach, and code changes are usually unnecessary. We also keep the configuration, which may be hidden somewhere in folders for Android and iOS. All you need to do is make appropriate changes to JSON files.

I didn’t touch on the issue of safety in the article. Honestly, none of the available front-end methods are completely secure. Remember to use --obfuscate when building your app. Additional data encryption can also increase security. Unfortunately, to put it mildly, there is no 100% certainty that some data will not be caught during reverse engineering. The worst thing you can do is deliver hard-coded secrets somewhere in the codebase.

Model-View-Controller (MVC)

The Model-View-Controller (MVC) framework is a design pattern that separates an application into three interconnected components: the Model, the View, and the Controller.

  • The Model represents the data and the rules that govern access to and updates of this data. In Rails, each model corresponds to a table in the database and is represented as a class.

  • The View is responsible for rendering the user interface, typically in HTML. It presents the information from the model in a format suitable for interaction.

  • The Controller acts as an intermediary between the Model and the View. It handles user input and updates the model, which in turn updates the view.

The MVC architecture in Rails enhances the extensibility of web applications, making it easier to manage and build applications, even complex ones. It also promotes code reusability and a clear definition of interfaces.

name
Krzysztof Kaiser
Head of Product Design at Monterail

Many people voice their reasonable concerns regarding the security of AI tools, but there’s also the topic of copyright. While the texts, and images generated by artificial intelligence are for now not deemed copyrightable, we need a discussion around intellectual property and how to protect human-authored works of art, books, and scientific studies and ensure that companies such as OpenAI will have to

Krzysztof Kaiser

Many people voice their reasonable concerns regarding the security of AI tools, but there’s also the topic of copyright. While the texts, and images generated by artificial intelligence are for now not deemed copyrightable, we need a

Many people voice their reasonable concerns regarding the security of AI tools, but there’s also the topic of copyright. While the texts, and images generated by artificial intelligence are for now not deemed copyrightable, we need a

Many people voice their reasonable concerns regarding the security of AI tools, but there’s also the topic of copyright. While the texts, and images generated by artificial intelligence are for now not deemed copyrightable, we need a

I believe great remote team cultures start with trust. You have to trust that people want to win and want to perform, and then find out what they need in order to do that. Often that comes down to offering structure and accountability and finding ways to reduce isolation and provide real human connection among colleagues. As a result, these things are central to the remote coworking product we're building at Focusmate.

1
It’s community-driven

Some skeptics may say that Vue is not a viable choice because it lacks the support from large tech corporations like Google and Facebook (and let’s not forget the issues with React licensing from years ago).

This opinion is truly belittling to the community standing behind Vue. Since its very beginning, more than 382 people from all over the world have contributed to the Vue.js repository.

Vue 3.2 „Quintessential Quintuplets” was officially released in August, 2021.

Some skeptics may say that Vue is not a viable choice because it lacks the support from large tech corporations like Google and Facebook (and let’s not forget the issues with React licensing from years ago).

2
It’s progressive

Some skeptics may say that Vue is not a viable choice because it lacks the support from large tech corporations like Google and Facebook (and let’s not forget the issues with React licensing from years ago).

This opinion is truly belittling to the community standing behind Vue. Since its very beginning, more than 382 people from all over the world have contributed to the Vue.js repository.

96%

survey participants would use Vue.js again for their next project.

93%

of the respondents used the Official Vue documentation as their main source of knowledge about the framework.

96%

of survey participants say that ease of integration is one of the chief advantages of having Vue in their organizations’ tech stack.

96%

are convinced Vue.js is going to get more popular in their organization in the next 12 months.