Our REST based application can be used for testing on multiple internal environments each with a different REST end point. Is there a simple way to set up environment level co
This is my approach of doing things when we have multiple end points. I used to make a ConfigurationManager
class something like this
Swift 3.0 code
import Foundation
import UIKit
let kEnvironmentsPlist:NSString? = "Environments"
let kConfigurationKey:NSString? = "ActiveConfiguration"
let kAPIEndpointKey:NSString? = "APIEndPoint"
let kLoggingEnabledKey:NSString? = "LoggingEnabled"
let kAnalyticsTrackingEnabled:NSString? = "AnalyticsTrackingEnabled"
class ConfigurationManager:NSObject {
var environment : NSDictionary?
//Singleton Method
static let sharedInstance: ConfigurationManager = {
let instance = ConfigurationManager()
// setup code
return instance
}()
override init() {
super.init()
initialize()
}
// Private method
func initialize () {
var environments: NSDictionary?
if let envsPlistPath = Bundle.main.path(forResource: "Environments", ofType: "plist") {
environments = NSDictionary(contentsOfFile: envsPlistPath)
}
self.environment = environments!.object(forKey: currentConfiguration()) as? NSDictionary
if self.environment == nil {
assertionFailure(NSLocalizedString("Unable to load application configuration", comment: "Unable to load application configuration"))
}
}
// CurrentConfiguration
func currentConfiguration () -> String {
let configuration = Bundle.main.infoDictionary?[kConfigurationKey! as String] as? String
return configuration!
}
// APIEndpoint
func APIEndpoint () -> String {
let configuration = self.environment![kAPIEndpointKey!]
return (configuration)! as! String
}
// isLoggingEnabled
func isLoggingEnabled () -> Bool {
let configuration = self.environment![kLoggingEnabledKey!]
return (configuration)! as! Bool
}
// isAnalyticsTrackingEnabled
func isAnalyticsTrackingEnabled () -> String {
let configuration = self.environment![kAnalyticsTrackingEnabled!]
return (configuration)! as! String
}
func applicationName()->String{
let bundleDict = Bundle.main.infoDictionary! as NSDictionary
return bundleDict.object(forKey: "CFBundleName") as! String
}
}
In Project--> Info Add some new configurations as per your need.
I have added Staging and QA as extra endpoints.Generally I use to make Staging as Release config and QA as Debug. So it will look like:
Now go to Targets -> Build Settings and add a User Defined Setting
Give the name of the user defined like ACTIVE_CONFIGURATION.
Add a key named ActiveConfiguration
in info.plist
with a variable name as $(ACTIVE_CONFIGURATION)
same as given in User Defined Settings with a $ in the beginning. We gave the name of key as ActiveConfiguration
because we are using the same name in our ConfigurationManager.swift
class for kConfigurationKey
.
let kConfigurationKey:NSString? = "ActiveConfiguration"
You can define as per your naming convention.
It will look like:
Now in the ConfigurationManager
class I am getting a path for Environments.plist
file.
I will just make a Environments.plist
file like this:
The actual description source of this file is
<?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>Development</key>
<dict>
<key>APIEndPoint</key>
<string>https://dev</string>
<key>LoggingEnabled</key>
<true/>
<key>AnalyticsTrackingEnabled</key>
<true/>
<key>Flurry</key>
<dict>
<key>FlurryApplicationID</key>
<string></string>
<key>FlurryApplicationSecret</key>
<string></string>
</dict>
<key>Facebook</key>
<dict>
<key>FacebookAppID</key>
<string></string>
<key>FacebookAppSecret</key>
<string></string>
</dict>
</dict>
<key>QA</key>
<dict>
<key>APIEndPoint</key>
<string>https://qa</string>
<key>LoggingEnabled</key>
<true/>
<key>AnalyticsTrackingEnabled</key>
<true/>
<key>Flurry</key>
<dict>
<key>FlurryApplicationID</key>
<string></string>
<key>FlurryApplicationSecret</key>
<string></string>
</dict>
<key>Facebook</key>
<dict>
<key>FacebookAppID</key>
<string></string>
<key>FacebookAppSecret</key>
<string></string>
</dict>
</dict>
<key>Staging</key>
<dict>
<key>APIEndPoint</key>
<string>https://staging</string>
<key>LoggingEnabled</key>
<false/>
<key>AnalyticsTrackingEnabled</key>
<true/>
<key>Flurry</key>
<dict>
<key>FlurryApplicationID</key>
<string></string>
<key>FlurryApplicationSecret</key>
<string></string>
</dict>
<key>Facebook</key>
<dict>
<key>FacebookAppID</key>
<string>840474532726958</string>
<key>FacebookAppSecret</key>
<string></string>
</dict>
</dict>
<key>Production</key>
<dict>
<key>APIEndPoint</key>
<string>https://production</string>
<key>LoggingEnabled</key>
<true/>
<key>AnalyticsTrackingEnabled</key>
<true/>
<key>Flurry</key>
<dict>
<key>FlurryApplicationID</key>
<string></string>
<key>FlurryApplicationSecret</key>
<string></string>
</dict>
<key>Facebook</key>
<dict>
<key>FacebookAppID</key>
<string></string>
<key>FacebookAppSecret</key>
<string></string>
</dict>
</dict>
</dict>
</plist>
We are now good to go. Now you have to just call
ConfigurationManager.sharedInstance.APIEndpoint()
for your respective end points.
Now you just have to change the schemes from Edit Schemes and you are done and change the Build Configuration in info.
This not only manages API End Points but also other things like whether to enable analytics or tracking for the respective end point or different ids of Facebook for different end points.
As Zac Kwan suggested, you can use different schemes to accomplish this, but you don't necessarily have to create a different configuration as well. Each scheme can specify unique environment variables. Then, access them from Swift:
let prodURL = "http://api.com"
let baseURL = ProcessInfo.processInfo.environment["BASE_URL"] ?? prodURL
I ended up using https://github.com/theappbusiness/ConfigGenerator:
A command line tool to auto-generate configuration file code, for use in Xcode projects. The configen tool is used to auto-generate configuration code from a property list. It is intended to create the kind of configuration needed for external URLs or API keys used by your app. Currently supports both Swift and Objective-C code generation.
I found that creating different Scheme and Configuration for your project works best. My setup is as follow:
I usually have 3 different scheme, MyApp-dev
, MyApp-staging
and MyApp
.
Each of the scheme
i created User-Defined-Attribute
to have different string appending to my Bundle Display Name
. So it can concurrently appear on my iOS device as MyApp-d
, MyApp-s
and MyApp
. Each also have its own Bundle ID
Then I create custom flags for each of them.
So in my Routes.swift
files i have something like this at the top:
#if PRODUCTION
static let hostName = "http://production.com/api/v1/"
#elseif STAGING
static let hostName = "http://staging.com/api/v1/"
#else
static let hostName = "http://development.com/api/v1/"
#endif
There is quite a few ways in how to update different hostname. But ultimately creating different Scheme and Configuration is always the first step.
Here is a few links that might help you get started:
https://medium.com/@danielgalasko/change-your-api-endpoint-environment-using-xcode-configurations-in-swift-c1ad2722200e#.o6nhic3pf
http://limlab.io/swift/2016/02/22/xcode-working-with-multiple-environments.html