Skip to content

Commit

Permalink
Introduce new approach for ios integration
Browse files Browse the repository at this point in the history
* ruby scripts were refactored
* environment values are now exposed to Info.plist via xcconfig file
* environment values became available in Build Settings
* cocoapods integration fixed - no need to add custom code to Podfile!
(as suggested in this PR: #329)
  • Loading branch information
maxkomarychev committed May 17, 2019
1 parent 7390b74 commit 1eb6ac0
Show file tree
Hide file tree
Showing 10 changed files with 164 additions and 120 deletions.
49 changes: 27 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ Module to expose config variables to your javascript code in React Native, suppo

Bring some [12 factor](http://12factor.net/config) love to your mobile apps!


## Basic Usage

Create a new file `.env` in the root of your React Native app:
Expand All @@ -17,15 +16,14 @@ GOOGLE_MAPS_API_KEY=abcdefgh
Then access variables defined there from your app:

```js
import Config from 'react-native-config'
import Config from "react-native-config";

Config.API_URL // 'https://myapi.com'
Config.GOOGLE_MAPS_API_KEY // 'abcdefgh'
Config.API_URL; // 'https://myapi.com'
Config.GOOGLE_MAPS_API_KEY; // 'abcdefgh'
```

Keep in mind this module doesn't obfuscate or encrypt secrets for packaging, so **do not store sensitive keys in `.env`**. It's [basically impossible to prevent users from reverse engineering mobile app secrets](https://rammic.github.io/2015/07/28/hiding-secrets-in-android-apps/), so design your app (and APIs) with that in mind.


## Setup

Install the package:
Expand All @@ -40,6 +38,11 @@ Link the library:
$ react-native link react-native-config
```

if cocoapods are used in the project then pod has to be installed as well:

```
(cd ios; pod install)
```

### Extra step for Android

Expand All @@ -50,17 +53,6 @@ You'll also need to manually apply a plugin to your app, from `android/app/build
apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle"
```


### Extra step for iOS (support Info.plist)

* Go to your project -> Build Settings -> All
* Search for "preprocess"
* Set `Preprocess Info.plist File` to `Yes`
* Set `Info.plist Preprocessor Prefix File` to `${BUILD_DIR}/GeneratedInfoPlistDotEnv.h`
* Set `Info.plist Other Preprocessor Flags` to `-traditional`
* If you don't see those settings, verify that "All" is selected at the top (instead of "Basic")


#### Advanced Android Setup

In `android/app/build.gradle`, if you use `applicationIdSuffix` or `applicationId` that is different from the package name indicated in `AndroidManifest.xml` in `<manifest package="...">` tag, for example, to support different build variants:
Expand Down Expand Up @@ -125,14 +117,27 @@ NSString *apiUrl = [ReactNativeConfig envFor:@"API_URL"];
NSDictionary *config = [ReactNativeConfig env];
```
They're also available for configuration in `Info.plist`, by prepending `__RN_CONFIG_` to their name:
#### Availability in Build settings and Info.plist
With one extra step environment values can be exposed to "Info.plist" and Build settings in the native project.
1. click on the file tree and create new file of type XCConfig
![img](./readme-pics/1.ios_new_file.png)
![img](./readme-pics/2.ios_file_type.png)
2. save it under `ios` folder as "Config.xcconfig" with the following content:
```
__RN_CONFIG_API_URL
#include? "tmp.xcconfig"
```
Note: Requires specific setup (see below) and a `Product > Clean` is required after changing the values to see the updated values.
3. go to project settings
4. apply config to your configurations
![img](./readme-pics/3.ios_apply_config.png)
5. create new build phase for the scheme which will generate "tmp.xcconfig" before each build exposing values to Build Settings and Info.plist<sup>this has to be placed after custom env file explained [here](#ios-multi-scheme)</sup>:
```
"${SRCROOT}/../node_modules/react-native-config/ios/ReactNativeConfig/BuildXCConfig.rb" "${SRCROOT}/.." "${SRCROOT}/tmp.xcconfig"
```
### Different environments
Expand All @@ -150,7 +155,6 @@ $ env:ENVFILE=".env.staging"; react-native run-ios # powershell
This also works for `run-android`. Alternatively, there are platform-specific options below.
#### Android
The same environment variable can be used to assemble releases with a different config:
Expand All @@ -171,9 +175,10 @@ project.ext.envConfigFiles = [
apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle"
```


#### iOS

<a name="ios-multi-scheme"></a>

The basic idea in iOS is to have one scheme per environment file, so you can easily alternate between them.

Start by creating a new scheme:
Expand All @@ -200,7 +205,7 @@ This is still a bit experimental and dirty – let us know if you have a better
When Proguard is enabled (which it is by default for Android release builds), it can rename the `BuildConfig` Java class in the minification process and prevent React Native Config from referencing it. To avoid this, add an exception to `android/app/proguard-rules.pro`:

-keep class com.mypackage.BuildConfig { *; }

`mypackage` should match the `package` value in your `app/src/main/AndroidManifest.xml` file.

## Testing
Expand Down
17 changes: 11 additions & 6 deletions ios/ReactNativeConfig.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,11 @@

/* Begin PBXFileReference section */
3DF7F6AC203AA09B00D0EAB7 /* libReactNativeConfig-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libReactNativeConfig-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
50406729228CAD5A00E0438A /* ReadDotEnv.rb */ = {isa = PBXFileReference; lastKnownFileType = text.script.ruby; path = ReadDotEnv.rb; sourceTree = "<group>"; };
50830C45228DD3D000CEA8FC /* BuildXCConfig.rb */ = {isa = PBXFileReference; lastKnownFileType = text.script.ruby; path = BuildXCConfig.rb; sourceTree = "<group>"; };
EB2648DF1C7BE17A00B8F155 /* libReactNativeConfig.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libReactNativeConfig.a; sourceTree = BUILT_PRODUCTS_DIR; };
EB2648E21C7BE17A00B8F155 /* ReactNativeConfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ReactNativeConfig.h; sourceTree = "<group>"; };
EBE4E8291C7BF6DD000F8875 /* BuildDotenvConfig.ruby */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = BuildDotenvConfig.ruby; sourceTree = "<group>"; };
EBE4E8291C7BF6DD000F8875 /* BuildDotenvConfig.rb */ = {isa = PBXFileReference; lastKnownFileType = text.script.ruby; path = BuildDotenvConfig.rb; sourceTree = "<group>"; };
EBE4E8461C7D2456000F8875 /* ReactNativeConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ReactNativeConfig.m; sourceTree = "<group>"; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -101,9 +103,11 @@
EB2648E11C7BE17A00B8F155 /* ReactNativeConfig */ = {
isa = PBXGroup;
children = (
50830C45228DD3D000CEA8FC /* BuildXCConfig.rb */,
EB2648E21C7BE17A00B8F155 /* ReactNativeConfig.h */,
EBE4E8461C7D2456000F8875 /* ReactNativeConfig.m */,
EBE4E8291C7BF6DD000F8875 /* BuildDotenvConfig.ruby */,
EBE4E8291C7BF6DD000F8875 /* BuildDotenvConfig.rb */,
50406729228CAD5A00E0438A /* ReadDotEnv.rb */,
);
path = ReactNativeConfig;
sourceTree = "<group>";
Expand Down Expand Up @@ -172,6 +176,7 @@
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
);
mainGroup = EB2648D61C7BE17A00B8F155;
Expand All @@ -192,27 +197,27 @@
files = (
);
inputPaths = (
"$(SRCROOT)/ReactNativeConfig/BuildDotenvConfig.ruby",
"$(SRCROOT)/ReactNativeConfig/BuildDotenvConfig.rb",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = ./ReactNativeConfig/BuildDotenvConfig.ruby;
shellScript = "./ReactNativeConfig/BuildDotenvConfig.rb\n";
};
EBE4E8281C7BF689000F8875 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 12;
files = (
);
inputPaths = (
"$(SRCROOT)/ReactNativeConfig/BuildDotenvConfig.ruby",
"$(SRCROOT)/ReactNativeConfig/BuildDotenvConfig.rb",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = ./ReactNativeConfig/BuildDotenvConfig.ruby;
shellScript = "set -xe\necho \"$SRCROOT\"\n./ReactNativeConfig/BuildDotenvConfig.rb \"$SRCROOT/../../..\" \"$SRCROOT/ReactNativeConfig/\"\n";
};
/* End PBXShellScriptBuildPhase section */

Expand Down
30 changes: 30 additions & 0 deletions ios/ReactNativeConfig/BuildDotenvConfig.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

require_relative 'ReadDotEnv'

envs_root = ARGV[0]
m_output_path = ARGV[1]
puts "reading env file from #{envs_root} and writing .m to #{m_output_path}"

# Allow utf-8 charactor in config value
# For example, APP_NAME=中文字符
Encoding.default_external = Encoding::UTF_8
Encoding.default_internal = Encoding::UTF_8

dotenv, custom_env = read_dot_env(envs_root)
puts "read dotenv #{dotenv}"

# create obj file that sets DOT_ENV as a NSDictionary
dotenv_objc = dotenv.map { |k, v| %(@"#{k}":@"#{v.chomp}") }.join(',')
template = <<EOF
#define DOT_ENV @{ #{dotenv_objc} };
EOF

# write it so that ReactNativeConfig.m can return it
path = File.join(m_output_path, 'GeneratedDotEnv.m')
File.open(path, 'w') { |f| f.puts template }

File.delete('/tmp/envfile') if custom_env

puts "Wrote to #{path}"
79 changes: 0 additions & 79 deletions ios/ReactNativeConfig/BuildDotenvConfig.ruby

This file was deleted.

13 changes: 13 additions & 0 deletions ios/ReactNativeConfig/BuildXCConfig.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

require_relative 'ReadDotEnv'

envs_root = ARGV[0]
config_output = ARGV[1]
puts "reading env file from #{envs_root} and writing .config to #{config_output}"

dotenv, custom_env = read_dot_env(envs_root)

dotenv_xcconfig = dotenv.map { |k, v| %(#{k}=#{v}) }.join("\n")
File.open(config_output, 'w') { |f| f.puts dotenv_xcconfig }
58 changes: 58 additions & 0 deletions ios/ReactNativeConfig/ReadDotEnv.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

# Allow utf-8 charactor in config value
# For example, APP_NAME=中文字符
Encoding.default_external = Encoding::UTF_8
Encoding.default_internal = Encoding::UTF_8

# TODO: introduce a parameter which controls how to build relative path
def read_dot_env(envs_root)
defaultEnvFile = '.env'
puts "going to read env file from root folder #{envs_root}"

# pick a custom env file if set
if File.exist?('/tmp/envfile')
custom_env = true
file = File.read('/tmp/envfile').strip
else
custom_env = false
file = ENV['ENVFILE'] || defaultEnvFile
end

dotenv = begin
# https://regex101.com/r/cbm5Tp/1
dotenv_pattern = /^(?:export\s+|)(?<key>[[:alnum:]_]+)=((?<quote>["'])?(?<val>.*?[^\\])\k<quote>?|)$/

path = File.expand_path(File.join(envs_root, file.to_s))
if File.exist?(path)
raw = File.read(path)
elsif File.exist?(file)
raw = File.read(file)
else
defaultEnvPath = File.expand_path(File.join(envs_root, "../#{defaultEnvFile}"))
unless File.exist?(defaultEnvPath)
# try as absolute path
defaultEnvPath = defaultEnvFile
end
defaultRaw = File.read(defaultEnvPath)
raw = defaultRaw + "\n" + raw if defaultRaw
end

raw.split("\n").inject({}) do |h, line|
m = line.match(dotenv_pattern)
next h if m.nil?

key = m[:key]
# Ensure string (in case of empty value) and escape any quotes present in the value.
val = m[:val].to_s.gsub('"', '\"')
h.merge(key => val)
end
rescue Errno::ENOENT
puts('**************************')
puts('*** Missing .env file ****')
puts('**************************')
return [{}, false] # set dotenv as an empty hash
end
[dotenv, custom_env]
end
Loading

0 comments on commit 1eb6ac0

Please sign in to comment.