Setting environment variables in Flutter - dart

For example, building a client for an API, like Twitch.
In a Dart CLI binary, I could use a generic environment variable, or a Dart definition variable. For example, using both as fallbacks:
main() {
String clientId =
// dart -dCLIENT_ID='abc bin/example.dart
// This is considered "compiled-into" the application.
const String.fromEnvironment('CLIENT_ID') ??
// CLIENT_ID='abc' dart bin/example.dart
// This is considered a runtime flag.
Platform.environment['CLIENT_ID'];
// Use clientId.
}
Does Flutter have a way of setting either/both of these, specifically...
During dev time
When shipped to prod
Happy to help with some docs once I figure out how :)

Starting from Flutter 1.17 you can define compile-time variables if you want to.
To do so just use --dart-define argument during flutter run or flutter build
If you need to pass multiple key-value pairs, just define --dart-define multiple times:
flutter run --dart-define=SOME_VAR=SOME_VALUE --dart-define=OTHER_VAR=OTHER_VALUE
and then, anywhere in your code you can use them like:
const SOME_VAR = String.fromEnvironment('SOME_VAR', defaultValue: 'SOME_DEFAULT_VALUE');
const OTHER_VAR = String.fromEnvironment('OTHER_VAR', defaultValue: 'OTHER_DEFAULT_VALUE');
Also, they can be used in native layers too.
Here is an article that explains more.

For configuration a common pattern I've seen is to use separate main files instead. i.e.
flutter run -t lib/production_main.dart
and
flutter build apk -t lib/debug_main.dart
And then in those different main files set up the configurations desired.
In terms of reading ids, you can do that from arbitrary assets https://flutter.io/assets-and-images/.
I believe it is possible in Flutter to read from the environment as you suggest, however I don't know how to set those environment variables on iOS or Android.

Since I was trying to solve this as well and encountered this thread I just wanted to add this for people looking for a solution in the future... If all you're looking for is PROD/DEV environments there is now a supported way of getting if the app is in production or not:
const bool isProduction = bool.fromEnvironment('dart.vm.product');
As suggested by:
https://twitter.com/FlutterDev/status/1048278525432791041
https://github.com/flutter/flutter/issues/4014

To run your app (in flutter run)
flutter run --dart-define=EXAMPLE_API_ENDPOINT=https://api.example.com/
To release your app (in flutter build)
My app wasn't letting users log in I realized that environment variables were empty strings in the app, instead of their actual values 😅.
iOS: flutter build ipa --dart-define=EXAMPLE_API_ENDPOINT=https://api.example.com/
Android: flutter build apk --dart-define=EXAMPLE_API_ENDPOINT=https://api.example.com/
--dart-define documentation
From the flutter run --help or flutter build ipa --help, the --dart-define shows:
Additional key-value pairs that will be available as
constants from the String.fromEnvironment, bool.fromEnvironment,
int.fromEnvironment, and double.fromEnvironment constructors.
Multiple defines can be passed by repeating "--dart-define"
multiple times.

I use simple shell script to generate dart defines. In my app there are 3 build flavors: dev, staging and prod. Environment variables were defined in a regular .env file.
env/
├── dev.env
├── prod.env
└── staging.env
Here is the script to generate dart-defines from .env file.
#!/bin/bash
# scripts/generate_dart_defines.sh
case "$1" in
"dev") INPUT="env/dev.env"
;;
"staging") INPUT="env/staging.env"
;;
"prod") INPUT="env/prod.env"
;;
*)
echo "Missing arguments [dev|staging|prod]"
exit 1
;;
esac
while IFS= read -r line
do
DART_DEFINES="$DART_DEFINES--dart-define=$line "
done < "$INPUT"
echo "$DART_DEFINES"
Here is the script to trigger a build.
#!/bin/bash
# build.sh
if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ]; then
echo -e "Missing arguments: [apk|appbundle|ios] [release|debug|profile] [dev|staging|prod]"
# invalid arguments
exit 128
fi
DART_DEFINES=$(scripts/generate_dart_defines.sh $3)
if [ $? -ne 0 ]; then
echo -e "Failed to generate dart defines"
exit 1
fi
echo -e "artifact: $1, type: $2, flavor: $3\n"
echo -e "DART_DEFINES: $DART_DEFINES\n"
eval "flutter build $1 --$2 --flavor $3 $DART_DEFINES"
The script accepts 3 arguments. First one is the artifact apk, appbundle or ios. Second one is the build type release, debug or profile. Third one is the build flavor, dev, staging or prod.
./build.sh apk release prod
Please note that you also required to configure android and ios for different build flavors separately.
https://developer.android.com/studio/build/build-variants
https://shockoe.com/ideas/development/how-to-setup-configurations-and-schemes-in-xcode/
https://developer.apple.com/library/archive/documentation/ToolsLanguages/Conceptual/Xcode_Overview/ManagingSchemes.html

I do agree with the answer posted by #tatsuDn but I wanted to provide a solution that loads your environment variables from a .env file.
First create a .env file in the root folder of your project.
Ensure that you add the file to your pubspec.yaml and [git] ignore it.
Here is how your .env file should look
API_KEY=sampleapikey
# This line is a comment
# The white line above will be ignored
HEADER=sampleapiheader
ANOTHER_UNIQUE_KEY=theValueOfThisKey
KEY_CONTAINS_#=*234*5#
KEY_CONTAINS_EQUALS=IP8iwe=0&
Here is how your assets section to look like.
# To add assets to your application, add an assets section, like this:
assets:
- assets/images/
- assets/flags/
- .env
Finally, load your environment variable by reading and parsing the .env file to get a Map<String, String> that contains your key value pairs.
Future<Map<String, String>> parseStringToMap({String assetsFileName = '.env'}) async {
final lines = await rootBundle.loadString(assetsFileName);
Map<String, String> environment = {};
for (String line in lines.split('\n')) {
line = line.trim();
if (line.contains('=') //Set Key Value Pairs on lines separated by =
&&
!line.startsWith(RegExp(r'=|#'))) {
//No need to add emty keys and remove comments
List<String> contents = line.split('=');
environment[contents[0]] = contents.sublist(1).join('=');
}
}
return environment;
}
You can put a quick button in your code to test that the environment variables are being loaded properly.
ElevatedButton(
onPressed: () async {
final env = await parseStringToMap(assetsFileName: '.env');
print(env);
},
child: Text('Print Environment Variables')
),
Here is the output from the .env file above.
>>>I/flutter ( 7182): {API_KEY: sampleapikey, HEADER: sampleapiheader, ANOTHER_UNIQUE_KEY: theValueOfThisKey, KEY_CONTAINS_#: *234*5#, KEY_CONTAINS_EQUALS: IP8iwe=0&}
Notes: You will need to rerun the app (not hot reload) so that the .env assets is loaded.
You can also just load your variables in a json file[this may be helpful when you have non string environemental variables and you dont want to parse string.
To avaoid IO, it is a good Idea to just load the environment variables once and access them through out the app using service locators like GetIt.

although above answers are correct coming from python and reactjs I used dotenv and found the same for flutter to load .env file
https://pub.dev/packages/dotenv

Create a class:
import 'package:flutter/foundation.dart';
class AppUtils {
static String get clientId {
if (kDebugMode) return 'debug_id';
else if (kProfileMode) return 'profile_id';
else if (kReleaseMode) return 'production_id';
else if (kIsWeb) return 'web_mode_id';
throw ArgumentError('No mode detected');
}
}
Usage:
var id = AppUtils.clientId;

Related

How to specify package/derivation runtime dependencies with Nix?

I'm making a haskell program and I'm setting buildInput like this to include pkgs.ffmpeg-full:
(myHaskellPackages.callCabal2nix "App" (./.) {}).overrideAttrs (oldAttrs: {
buildInputs = (oldAttrs.buildInputs or []) ++ [ pkgs.ffmpeg-full ];
})
However this seems to make the ffmpeg package accessible during build time only rather than runtime of the application.
What attribute do I need to set for ffmpeg-full to be available during runtime - being able to invoke the ffmpeg executable?
There is a section about runtime dependencies in nix pills but I don't understand that section, it doesn't make sense how it can always determine runtime dependencies by hashes alone? I mean if I reference an executable in a shell script - surely nix does not parse the shell script to determine the executable I reference. https://nixos.org/guides/nix-pills/automatic-runtime-dependencies.html#idm140737320205792
Something is different for runtime dependencies however. Build
dependencies are automatically recognized by Nix once they are used in
any derivation call, but we never specify what are the runtime
dependencies for a derivation.
There's really black magic involved. It's something that at first
glance makes you think "no, this can't work in the long term", but at
the same time it works so well that a whole operating system is built
on top of this magic.
In other words, Nix automatically computes all the runtime
dependencies of a derivation, and it's possible thanks to the hash of
the store paths.
default.nix:
{
ghc ? "ghc8106",
pkgs ? import <nixpkgs> {}
}:
with pkgs.haskell.lib;
let
haskellPkgs = pkgs.haskell.packages.${ghc};
inherit (pkgs) lib;
mySourceRegexes = [
"^app.*$"
"^.*\\.cabal$"
"package.yaml"
];
myApp = (haskellPkgs.callCabal2nix "my-hello"
(lib.sourceByRegex ./. mySourceRegexes) { });
in myApp
.overrideAttrs(
oa: {
nativeBuildInputs = oa.nativeBuildInputs ++ [pkgs.hello pkgs.makeWrapper];
installPhase = oa.installPhase + ''
ln -s ${pkgs.hello.out}/bin/hello $out/bin/hello
'';
postFixup = ''
wrapProgram $out/bin/x-exe --prefix PATH : ${pkgs.lib.makeBinPath [ pkgs.hello ]}
'';
})
src/Main.hs:
module Main where
import System.Process (callCommand)
main :: IO ()
main = do
putStrLn "HELLO"
callCommand "hello"
putStrLn "BYE"
Seems this is not directly supported with an explicitly stated list of dependencies. However we can indirectly achieve this with "wrapping".
I found more information about wrapping here: https://nixos.wiki/wiki/Nix_Cookbook#Wrapping_packages
So I can do a ls that references the package.
...
appPkg = (myHaskellPackages.callCabal2nix "HaskellNixCabalStarter" (./.) {}).overrideAttrs (oldAttrs: {
buildInputs = (oldAttrs.buildInputs or []) ++ [ pkgs.ffmpeg-full ];
});
in
(pkgs.writeScriptBin "finderapp" ''
#!${pkgs.stdenv.shell}
ls ${pkgs.ffmpeg-full}/bin/ffmpeg
exec ${appPkg}/bin/app
''
)
We can verify the built package(?) correctly depends on the appropriate with:
nix-store -q --references result
/nix/store/0cq84xic2absp75ciajv4lfx5ah1fb59-ffmpeg-full-4.2.2
/nix/store/rm1hz1lybxangc8sdl7xvzs5dcvigvf7-bash-4.4-p23
/nix/store/wlvnjx53xfangaa4m5rmabknjbgpvq3d-HaskellNixCabalStarter-0.1.0.0

How do I make a bazel `sh_binary` target depend on other binary targets?

I have set up bazel to build a number of CLI tools that perform various database maintenance tasks. Each one is a py_binary or cc_binary target that is called from the command line with the path to some data file: it processes that file and stores the results in a database.
Now, I need to create a dependent package that contains data files and shell scripts that call these CLI tools to perform application-specific database operations.
However, there doesn't seem to be a way to depend on the existing py_binary or cc_binary targets from a new package that only contains sh_binary targets and data files. Trying to do so results in an error like:
ERROR: /workspace/shbin/BUILD.bazel:5:12: in deps attribute of sh_binary rule //shbin:run: py_binary rule '//pybin:counter' is misplaced here (expected sh_library)
Is there a way to call/depend on an existing bazel binary target from a shell script using sh_binary?
I have implemented a full example here:
https://github.com/psigen/bazel-mixed-binaries
Notes:
I cannot use py_library and cc_library instead of py_binary and cc_binary. This is because (a) I need to call mixes of the two languages to process my data files and (b) these tools are from an upstream repository where they are already designed as CLI tools.
I also cannot put all the data files into the CLI tool packages -- there are multiple application-specific packages and they cannot be mixed.
You can either create a genrule to run these tools as part of the build, or create a sh_binary that depends on the tools via the data attribute and runs them them.
The genrule approach
This is the easier way and lets you run the tools as part of the build.
genrule(
name = "foo",
tools = [
"//tool_a:py",
"//tool_b:cc",
],
srcs = [
"//source:file1",
":file2",
],
outs = [
"output_file1",
"output_file2",
],
cmd = "$(location //tool_a:py) --input=$(location //source:file1) --output=$(location output_file1) && $(location //tool_b:cc) < $(location :file2) > $(location output_file2)",
)
The sh_binary approach
This is more complicated, but lets you run the sh_binary either as part of the build (if it is in a genrule.tools, similar to the previous approach) or after the build (from under bazel-bin).
In the sh_binary you have to data-depend on the tools:
sh_binary(
name = "foo",
srcs = ["my_shbin.sh"],
data = [
"//tool_a:py",
"//tool_b:cc",
],
)
Then, in the sh_binary you have to use the so-called "Bash runfiles library" built into Bazel to look up the runtime-path of the binaries. This library's documentation is in its source file.
The idea is:
the sh_binary has to depend on a specific target
you have to copy-paste some boilerplate code to the top of the sh_binary (reason is described here)
then you can use the rlocation function to look up the runtime-path of the binaries
For example your my_shbin.sh may look like this:
#!/bin/bash
# --- begin runfiles.bash initialization ---
...
# --- end runfiles.bash initialization ---
path=$(rlocation "__main__/tool_a/py")
if [[ ! -f "${path:-}" ]]; then
echo >&2 "ERROR: could not look up the Python tool path"
exit 1
fi
$path --input=$1 --output=$2
The __main__ in the rlocation path argument is the name of the workspace. Since your WORKSPACE file does not have a "workspace" rule in, which would define the workspace's name, Bazel will use the default workspace name, which is __main__.
An easier approach for me is to add the cc_binary as a dependency in the data section. In prefix/BUILD
cc_binary(name = "foo", ...)
sh_test(name = "foo_test", srcs = ["foo_test.sh"], data = [":foo"])
Inside foo_test.sh, the working directory is different, so you need to find the right prefix for the binary
#! /usr/bin/env bash
executable=prefix/foo
$executable ...
A clean way to do this is to use args and $(location):
Contents of BUILD:
py_binary(
name = "counter",
srcs = ["counter.py"],
main = "counter.py",
)
sh_binary(
name = "run",
srcs = ["run.sh"],
data = [":counter"],
args = ["$(location :counter)"],
)
Contents of counter.py (your tool):
print("This is the counter tool.")
Contents of run.sh (your bash script):
#!/bin/bash
set -eEuo pipefail
counter="$1"
shift
echo "This is the bash script, about to call the counter tool."
"$counter"
And here's a demo showing the bash script calling the Python tool:
$ bazel run //example:run 2>/dev/null
This is the bash script, about to call the counter tool.
This is the counter tool.
It's also worth mentioning this note (from the docs):
The arguments are not passed when you run the target outside of bazel (for example, by manually executing the binary in bazel-bin/).

How to record a reproducible profile in nix (especially from nix-env)?

So, finally starting to get a stable nix environment that I can basically do all of my development in. Hooray!
Now I want to make it reproducible, as in yarn.lock (for those familiar with npm/yarn in javascript land) or Pipfile.lock (very similar for Python).
Basically the idea is that I would have a way to generate a similar lock file whenever I run nix-env -if my-env.nix, or after running this command, if that is how it would work. From this lock file, I could then exactly restore my nix profile, down to the exact versions of dependencies and sub-dependencies of the installed profile. This could be checked into git or whatever after testing out new improvements, and so a record of the environment would be maintained.
It seems to me this would be one of the most obvious use cases for Nix, and one of the major advantages over just using Docker (though the two aren't mutually exclusive), so I apologize if I've missed some relevant documentation.
What you're probably looking for is a shell.nix file like this:
let
pkgs = import (fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/696c6bed4e8e2d9fd9b956dea7e5d49531e9d13f.tar.gz";
sha256 = "1v3yrpj542niyxp0h3kffsdjwlrkvj0mg4ljb85d142gyn3sdzd4";
}) {};
in pkgs.mkShell {
buildInputs = with pkgs; [
git
hello
];
}
Upon calling nix-shell (which by default uses the shell.nix file in the current directory), you'll be in an environment with git and hello from the specific given nixpkgs revision. This can be reproduced among all nix users (okay almost*). I can really recommend using shell.nix files for all development.
Alternatively, there's also the lesser known -r flag to nix-env, which states that
--remove-all, -r
Remove all previously installed packages first. This is equivalent to running nix-env -e '.*' first, except that everything happens in a single transaction.
You can effectively use it to replace the stateful ~/.nix-profile/manifest.nix. Create a file env.nix containing:
let
pkgs = import <nixpkgs> {};
in {
inherit (pkgs) git hello;
}
Now running nix-env -ir env.nix will install exactly git and hello and remove everything else, so you can reproduce your nix-env installations with this single file. To install additional things: Add it to the file and run the command again. You can also pin nixpkgs to a specific version as in the file above to not care about your nix-channel setup (which is also stateful).
Edit: It's also possible to get some packages from your channel and some of a specific nixpkgs revision:
let
pkgs = import <nixpkgs> {};
fixed = import (fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/696c6bed4e8e2d9fd9b956dea7e5d49531e9d13f.tar.gz";
sha256 = "1v3yrpj542niyxp0h3kffsdjwlrkvj0mg4ljb85d142gyn3sdzd4";
}) {};
in {
inherit (pkgs) git;
inherit (fixed) hello;
}
*: The nix config, overlays and your system (Linux/Mac) can still influence this. It's a good idea to use import <nixpkgs> { config = {}; overlays = []; } for development to avoid this.
After many suggestions from folks on IRC, I was able to put together the following script, which takes a nix expression as its only argument, and copies the derivation file and records the nixpkgs versions locally while installing the specified environment:
#!/bin/bash
if [ -z "$1" ] || [ "${1: -4}" != ".nix" ] || [ ! -f "$1" ]
then
echo "No .nix file supplied"
exit -1
fi
ENV_DRV=$(nix-instantiate "$1")
cp "$ENV_DRV" ./env_backup.drv
chmod u+rw ./env_backup.drv
nix-env --set "$ENV_DRV"
NIXPKGS_VERSION=$(nix-instantiate --eval '<nixpkgs/lib>' -A version)
NIXOS_VERSION=$(nix-instantiate --eval '<nixos/lib>' -A version)
printf "nixpkgs: %s\\nnixos: %s" "$NIXPKGS_VERSION" "${NIXOS_VERSION}" > .nix_versions
Caveat: your nix expression should have the nix package in it, since we are using nix-env --set.
I haven't tried using a copied .drv file yet, but it somehow needs to be restored into the nix store; I've mainly included it as a last resort and for debugging. The output into .nix_versions should be more helpful, as these contain git commit hashes (after the last ".") that can be used to employ the correct revision of nixpkgs (thanks to infinisil on IRC):
pkgs = import "${(import <nixpkgs> {}).fetchFromGitHub { owner = "NixOS"; repo = "nixpkgs"; rev = "<your revision hash>"; sha256 = "<the hash of the output>"; }}" {}
To fill in the hash, either supply an incorrect hash to get the correct hash back, or just use the following: nix-prefetch-url --unpack github.com/nixos/nixpkgs/archive/<revision>.tar.gz.
Or, if manually checking out nixpkgs, you can just do e.g.:
with import ((builtins.getEnv "HOME") + "/workspace/nixpkgs") { }; # or:
with import "../nixpkgs" { }; # or similar
I haven't tested this sort of thing with nix-shell yet, but hope to do so soon.

How can I get an environment variable from Xcode in my app

I have installed the Xcode plugin for XcodeColors from robbie hanson.
(see https://github.com/robbiehanson/XcodeColors)
If I test it in a playground
let dict = NSProcessInfo.processInfo().environment
let env = dict["XcodeColors"] as? String
env would be "YES".
But, if I use the same code in my app, env would be nil, because the app is running on their own process.
Because I would print out colored text with specific esc sequences only if the plugin is installed, I want get the information about the Xcode env var.
How can I do that?
Edit your scheme -> Select the "Run" section -> Select "Arguments" tab -> Add the environment variable.
Be careful, environment variables are not set if you run the app without XCode.
I ran into the same problem with XcodeColors. I ended up solving it with a simple script build phase. It checks to see if XcodeColors is installed or not and sets/adds a key to the Info.plist in the build. So create a new "Run Script Build Phase" and put this in there:
xcodeColorsDir="$USER_LIBRARY_DIR/Application Support/Developer/Shared/Xcode/Plugins/XcodeColors.xcplugin/"
xcodeColorsInstalled=0
if [ -d "$xcodeColorsDir" ]; then
# Directory exists, therefore, XcodeColors is installed
xcodeColorsInstalled=1
fi
infoPlistPath="${TARGET_BUILD_DIR}/${INFOPLIST_PATH}"
existingValue=$(/usr/libexec/PlistBuddy -c "Print :XcodeColorsInstalled" "$infoPlistPath")
if [ -z "$existingValue" ]; then
# Key already exists so overwrite it
/usr/libexec/PlistBuddy -c "Add :XcodeColorsInstalled bool $xcodeColorsInstalled" "$infoPlistPath"
else
# Key doesn't exist yet
/usr/libexec/PlistBuddy -c "Set :XcodeColorsInstalled $xcodeColorsInstalled" "$infoPlistPath"
fi
Then, you can access the Info.plist param during runtime with something like:
func isColorizedLoggingEnabled() -> Bool {
if let colorizedLoggingEnabled = NSBundle.mainBundle().infoDictionary?["XcodeColorsInstalled"] as? Bool {
return colorizedLoggingEnabled
} else {
return false
}
}

Add DATE to Xcode xcconfig file

I have an Xcode config file, Config.xcconfig that contains this row only:
BUILD_DATE=`date "+%B %Y"`
I added this configuration to project in correct way, i hope.
I want to use the content of BUILD_DATE variable in the Application-info.plist file. How?
I tried get value using ${BUILD_DATE} but result is the string ``date "+%B %Y"` not the value!
From terminal, result is correct:
alp$ BUILD_DATE=`date "+%B %Y"`
alp$ echo $BUILD_DATE
March 2013
alp$
but in Xcode no!
How can i fix this?
You cannot get the build date using the backtick command as the .xcconfig file is not interpreted as a shell script.
Your best bet is to use a similar approach the Bump Build Number script in this SO question (that I asked a while back), which provides a solution for using an external build script to update the .plist file.
For example:
#!/bin/sh
if [ $# -ne 1 ]; then
echo usage: $0 plist-file
exit 1
fi
plist="$1"
build_date=$(date "+%B %Y")
/usr/libexec/Plistbuddy -c "Set BUILD_DATE \"$build_date\"" "$plist"
and invoke it from the Xcode Build Script using something like:
"${PROJECT_DIR}/tools/set_build_date.sh" "${PROJECT_DIR}/${INFOPLIST_FILE}"

Resources