Add Firebase Analytics and Crashlytics to iOS project with multiple schemes - ios

I have a projects with multiple schemes (Not targets).
I have Dev, QA and Prod and I want to add Firebase Analytics and Crashlytics to all of the schemes note that each scheme has its own Bundle id and different name.
How can I achieve this this?

if you have multple scheme on just one target:
you can change plist files following the scheme like this:
#if DEV_DEBUG || DEV_RELEASE
let filePath = Bundle.main.path(forResource: "GoogleService-Info-Dev", ofType: "plist")
#else
let filePath = Bundle.main.path(forResource: "GoogleService-Info", ofType: "plist")
#endif
guard let fileopts = FirebaseOptions(contentsOfFile: filePath!)
else { assert(false, "Couldn't load config file") }
FirebaseApp.configure(options: fileopts)
in this code I have two schem one is Dev other is Prod

Tested on Xcode 13.3.X
Assuming you already have a Google Firebase account and opened an app in the Firebase console add an app in the console for iOS.
Follow the steps and register your Bundle id for the app now for each scheme (Dev, QA and Prod) you will need to register a different app with different Bundle id and download the GoogleService-Info.plist file DO NOT rename the Plist files.
In your Xcode project Create separate folders for each environment drag each GoogleService-Info.plist files to their folder and Uncheck Copy to target.
In your pod file add pod 'Firebase/Crashlytics' (if you are also using analytics add the pod) and run pod install in the terminal.
After this go to pods target (this is a bug that google suggested a workaround for) and search Apple Clang - Warnings - All Languages and set Quoted include in Framework Header to NO).
After this Go to your target Build Settings under Build Options -> Debug Information Format set all to :
DWARF with dSYM File
On Build Phase tab in the Target add 2 Run Scripts.
The first call Firebase Plist selector (or any other name you want just make sure it runs BEFORE the script to upload the dSYM) and add the following script :
INFO_PLIST=GoogleService-Info.plist
DEVELOPMENT_INFO_PLIST=${PROJECT_DIR}/${TARGET_NAME}/Environment/Dev/${INFO_PLIST}
QA_INFO_PLIST=${PROJECT_DIR}/${TARGET_NAME}/Environment/QA/${INFO_PLIST}
PROD_INFO_PLIST=${PROJECT_DIR}/${TARGET_NAME}/Environment/Prod/${INFO_PLIST}
echo "DEV -> CHECKING in development! ${INFO_PLIST} in ${DEVELOPMENT_INFO_PLIST}"
if [ ! -f $DEVELOPMENT_INFO_PLIST ] ; then
echo "DEV GoogleService-Info.plist not found."
exit 1
fi
echo "QA -> CHECKING in QA ${INFO_PLIST} in ${QA_INFO_PLIST}"
if [ ! -f $QA_INFO_PLIST ] ; then
echo "QA GoogleService-Info.plist not found."
exit 1
fi
echo "PROD -> CHECKING in PROD ${INFO_PLIST} in ${PROD_INFO_PLIST}"
if [ ! -f $PROD_INFO_PLIST ] ; then
echo "PROD GoogleService-Info.plist not found."
exit 1
fi
PLIST_DESTINATION=${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app
echo "Copying ${INFO_PLIST} to final destination: ${PLIST_DESTINATION}"
elif [ "${CONFIGURATION}" == "QA MyProject" ] ; then
echo "QA -> Copied FILE : ${QA_INFO_PLIST}."
cp "${QA_INFO_PLIST}" "${PLIST_DESTINATION}"
elif [ "${CONFIGURATION}" == "Prod MyProject" ] ; then
echo "PROD -> Copied FILE : ${PROD_INFO_PLIST}."
cp "${PROD_INFO_PLIST}" "${PLIST_DESTINATION}"
else
echo "DEV -> Copied ${DEVELOPMENT_INFO_PLIST}."
cp "${DEVELOPMENT_INFO_PLIST}" "${PLIST_DESTINATION}"
fi
Here you are checking for the GoogleService-Info.plist file for each scheme (note where it says /Environment/Dev QA Prod etc change it to your folder path) if the file is found then it will be added in build time and the correct Plist file will be added to the build each time.
Now in the second script add this:
"${PODS_ROOT}/FirebaseCrashlytics/run"
And under Input Files add these 2:
$(SRCROOT)/${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${TARGET_NAME}
$(SRCROOT)/$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)
Clean and build project if everything is correct when your enter the Crashlytics part in the console and simulate a crash (you can put fatalError on a IBAction or Button action to simulate) and you will be able to see your crash for each scheme you configured.
As a note if you wish to copy a folder use :
cp -R
This will copy the folder and all its contents.
Very important to add a / at the end of the name for example change
INFO_PLIST=GoogleService-Info.plist
to
INFO_PLIST=MYFOLDERNAME/
Kindest regards.

Related

Add two GoogleService-Info.plist for Prod and dev build schemes

before everything, I have already read this post
Use different GoogleService-Info.plist for different build schemes
But still somehow it's not working for me.
I have two Prod and dev build schemes and I have two different GoogleService-Info.plistfor them.
In my first attempt, I rename both google files to GoogleService-Info-prodand GoogleService-Info-dev. and tried to read the files directly:
#if Production
if let filePath = Bundle.main.path(forResource: "GoogleService-Info-prod", ofType: "plist"),
let options = FirebaseOptions(contentsOfFile: filePath) {
FirebaseApp.configure(options: options)
} else {
fatalError("GoogleService-Info-Dev.plist is missing!")
}
#elseif Development
if let filePath = Bundle.main.path(forResource: "GoogleService-Info-dev", ofType: "plist"),
let options = FirebaseOptions(contentsOfFile: filePath) {
FirebaseApp.configure(options: options)
} else {
fatalError("GoogleService-Info.plist is missing!")
}
#endif
FirebaseConfiguration.shared.setLoggerLevel(.min)
UNUserNotificationCenter.current().delegate = self
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(options: authOptions) { _, _ in }
application.registerForRemoteNotifications()
Messaging.messaging().delegate = self
App can run ok after that. But in logs I have this warning
[FirebaseCore][I-COR000012] Could not locate configuration file: 'GoogleService-Info.plist'.
So It can mean that filename must be GoogleService-Info.plist and it doesn't work with GoogleService-Info-prod and GoogleService-Info-dev
In my second attempt, I made all the two files to GoogleService-Info.plist, so it leads to have an error:
"Multiple commands produce GoogleService-Info.plist"
To avoid this error, I removed these two files from Build Phases > Copy Bundle Resources
and make the firebase setup function like that:
FirebaseApp.configure()
FirebaseConfiguration.shared.setLoggerLevel(.min)
UNUserNotificationCenter.current().delegate = self
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(options: authOptions) { _, _ in }
application.registerForRemoteNotifications()
Messaging.messaging().delegate = self
And then add this script
GOOGLESERVICE_INFO_DEV=${PROJECT_DIR}/${TARGET_NAME}/Application/Firebase/Dev/${GOOGLESERVICE_INFO_PLIST}
GOOGLESERVICE_INFO_PROD=${PROJECT_DIR}/${TARGET_NAME}/Application/Firebase/Prod/${GOOGLESERVICE_INFO_PLIST}
# Make sure the dev version of GoogleService-Info.plist exists
echo "Looking for ${GOOGLESERVICE_INFO_PLIST} in ${GOOGLESERVICE_INFO_DEV}"
if [ ! -f $GOOGLESERVICE_INFO_DEV ]
then
echo "No Development GoogleService-Info.plist found. Please ensure it's in the proper directory."
exit 1
fi
# Make sure the prod version of GoogleService-Info.plist exists
echo "Looking for ${GOOGLESERVICE_INFO_PLIST} in ${GOOGLESERVICE_INFO_PROD}"
if [ ! -f $GOOGLESERVICE_INFO_PROD ]
then
echo "No Production GoogleService-Info.plist found. Please ensure it's in the proper directory."
exit 1
fi
# Get a reference to the destination location for the GoogleService-Info.plist
PLIST_DESTINATION=${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app
echo "Will copy ${GOOGLESERVICE_INFO_PLIST} to final destination: ${PLIST_DESTINATION}"
# Copy over the prod GoogleService-Info.plist for Release builds
if [ "${CONFIGURATION}" == "Release" ]
then
echo "Using ${GOOGLESERVICE_INFO_PROD}"
cp "${GOOGLESERVICE_INFO_PROD}" "${PLIST_DESTINATION}"
else
echo "Using ${GOOGLESERVICE_INFO_DEV}"
cp "${GOOGLESERVICE_INFO_DEV}" "${PLIST_DESTINATION}"
fi
BTW: I have to check the For install builds only, otherwise I will get error:
Command PhaseScriptExecution failed with a nonzero exit code
Then after run, app will crash, and it says firebase cannot find GoogleService-Info.plist- it's because I remove then from Build Phases > Copy Bundle Resources to avoid multiple file name issue.
Can anyone help how to fix this issue and add google files successfully.
BTW: I don't care about any Firebase analytics
Thank you so much
I've implemented this in one of the apps I worked on. You're close to solving it. The way I did it was to add different GoogleService-Info.plist files in my project (I had 4 schemes and added 4 files) under separate folders. These files were not included in any target (all targets unchecked in Target Membership under Identity Inspector), they were just a part of the project.
I wrote a build phase script to copy the appropriate file based on my configuration (which in-turn is linked to my schemes) to the build. Here's an abridged version of the script that will give you an idea of how to go about it
FILENAME=GoogleService-Info.plist
DESTINATION=${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app
if [ "${CONFIGURATION}" == "MY_CONFIG_1" ]
then
CONFIGFILE1=${PROJECT_DIR}/MYPROJ/CONFIG1DIRECTORY/${FILENAME}
if [ ! -f $CONFIGFILE1 ]
then
echo "File not found"
exit 1
fi
cp "${CONFIGFILE1}" "${DESTINATION}"
elif [ "${CONFIGURATION}" == "MY_CONFIG_2" ]
then
CONFIGFILE2=${PROJECT_DIR}/MYPROJ/CONFIG2DIRECTORY/${FILENAME}
if [ ! -f $CONFIGFILE2 ]
then
echo "File not found"
exit 1
fi
cp "${CONFIGFILE2}" "${DESTINATION}"
fi
The key here is that I'm using the name GoogleService-Info.plist for all 4 files, and just copying the required one to the app while building.

iOS App -On firebase console I am not able to log the events from real users whereas the events are getting properly logged in debug view

I have implemented firebase in my already live iOS application with multiple environments (can say - PROD and PrePROD) both are having individual project settings in firebase.
For PrePROD the data is showing up correctly however for PROD enviroment I am able to see events in debug view using simulator but we are unable to track data for live Users.
I understand that event sometime takes time to get logged but I have waited enough to see the progress.
Firebase runscript that I have added in the project which seems to be working fine as I am able to track simulator data on debug views
# Name of the resource we're selectively copying
GOOGLESERVICE_INFO_PLIST=GoogleService-Info.plist
# Get references to dev and prod versions of the GoogleService-Info.plist
# NOTE: These should only live on the file system and should NOT be part of the target (since we'll be adding them to the target manually)
GOOGLESERVICE_INFO_DEV=${PROJECT_DIR}/${TARGET_NAME}/Config/FirebaseConfigFiles/Dev/${GOOGLESERVICE_INFO_PLIST}
GOOGLESERVICE_INFO_PROD=${PROJECT_DIR}/${TARGET_NAME}/Config/FirebaseConfigFiles/Prod/${GOOGLESERVICE_INFO_PLIST}
# Make sure the dev version of GoogleService-Info.plist exists
echo "Looking for ${GOOGLESERVICE_INFO_PLIST} in ${GOOGLESERVICE_INFO_DEV}"
if [ ! -f $GOOGLESERVICE_INFO_DEV ]
then
echo "No Development GoogleService-Info.plist found. Please ensure it's in the proper directory."
exit 1
fi
# Make sure the prod version of GoogleService-Info.plist exists
echo "Looking for ${GOOGLESERVICE_INFO_PLIST} in ${GOOGLESERVICE_INFO_PROD}"
if [ ! -f $GOOGLESERVICE_INFO_PROD ]
then
echo "No Production GoogleService-Info.plist found. Please ensure it's in the proper directory."
exit 1
fi
# Get a reference to the destination location for the GoogleService-Info.plist
PLIST_DESTINATION=${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app
echo "Will copy ${GOOGLESERVICE_INFO_PLIST} to final destination: ${PLIST_DESTINATION}"
echo ${CONFIGURATION}
# Copy over the prod GoogleService-Info.plist for Release builds
if [ "${CONFIGURATION}" == "Appstore" ] || [ "${CONFIGURATION}" == "Release(PROD)" ] || [ "${CONFIGURATION}" == "Debug(PROD)" ]
then
echo "Using ${GOOGLESERVICE_INFO_PROD}"
cp "${GOOGLESERVICE_INFO_PROD}" "${PLIST_DESTINATION}"
else
echo "Using ${GOOGLESERVICE_INFO_DEV}"
cp "${GOOGLESERVICE_INFO_DEV}" "${PLIST_DESTINATION}"
fi
Run script Logs:
Runscript logs
Scheme Details:
Scheme details
Analytics tracking Method:
func track(_ eventName: String, properties: [String: Any]? = nil) {
if let localProps = properties {
Analytics.logEvent(eventName, parameters: localProps)
} else {
Analytics.logEvent(eventName, parameters: nil)
}
Please note I am still able to see the simulator events on debug view for PROD environment but there is no trace of events happening on real devices for real users. And for the same project I am able to see data for android aswell
I need help from you guys to tell me if is there anything I am missing in setting up the project or while archiving the build for the appstore release.
Let me know If you require more details regarding the implementation.
Looking forward for some solutions. I am stuck in this since so long

Flutter: IPA build finds the GoogleService-Info.plist in folder A but it does not find it in folder B?

I have a pipeline that should build me 2 IPA files with different schemes and firebase connection but meanwhile the first type file built successfully:
The second throws this:
Xcode build done. 67.7s
Failed to build iOS app
Error output from Xcode build:
↳
** BUILD FAILED **
Xcode's output:
↳
Writing result bundle at path:
/var/folders/24/8k48jl6d249_n_qfxwsl6xvm0000gn/T/flutter_tools.ZgjruG/flutter_ios_build_temp_dirXYq2SP/temporary_xcresult_bundle
Configuration is: uat-type
cp: /Users/runner/work/1/s/flavors/ios/Runner/Firebase/uat/GoogleService-Info.plist: No such file or directory
Command PhaseScriptExecution failed with a nonzero exit code
note: Using new build system
note: Planning
note: Build preparation complete
note: Building targets in dependency order
Result bundle written to path:
/var/folders/24/8k48jl6d249_n_qfxwsl6xvm0000gn/T/flutter_tools.ZgjruG/flutter_ios_build_temp_dirXYq2SP/temporary_xcresult_bundle
Encountered error while building for device.
##[error]Error: The process '/Users/runner/hostedtoolcache/Flutter/3.3.2/macos/flutter/bin/flutter' failed with exit code 1
Finishing: Flutter Build App - iOS
As you can see the files in XCode can be seen:
It finds the file at Firebase/cicd but not at Firebase/uat.
This is the script I use to copy the file based on configuration:
if [ "${CONFIGURATION}" == "Debug-cicd" ] || [ "${CONFIGURATION}" == "Release-cicd" ]; then cp -r "${PROJECT_DIR}/Runner/Firebase/cicd/GoogleService-Info.plist" "${PROJECT_DIR}/Runner/GoogleService-Info.plist"
fi
if [ "${CONFIGURATION}" == "Debug-uat" ] || [ "${CONFIGURATION}" == "Release-uat" ]; then cp -r "${PROJECT_DIR}/Runner/Firebase/uat/GoogleService-Info.plist" "${PROJECT_DIR}/Runner/GoogleService-Info.plist"
fi
I wonder if anyone has any idea what could be the problem, what should I try to make it work?
Thanks in advance.

iOS Umbrella Framework - codesign problem

I have an Umbrella Framework distributed throughs Cocoapods as vendored framework and compiled in release mode.
It works perfectly with simulator, but I have a problem with the code sign on the sub-framework nested in the umbrella layer.
This is the error:
dyld: Library not loaded: #rpath/Subframework.framework/Subframework
Referenced from: /private/var/containers/Bundle/Application/02AD328F-9E78-4D53-9C39-0C8639B00D81/sdkInteTest.app/Frameworks/Umbrella.framework/Umbrella
Reason: no suitable image found. Did find:
/private/var/containers/Bundle/Application/02AD328F-9E78-4D53-9C39-0C8639B00D81/sdkInteTest.app/Frameworks/Umbrella.framework/Frameworks/Subframework.framework/Subframework: code signature in (/private/var/containers/Bundle/Application/02AD328F-9E78-4D53-9C39-0C8639B00D81/sdkInteTest.app/Frameworks/Umnrella.framework/Frameworks/Subframework.framework/Subframework) not valid for use in process using Library Validation: mapped file has no cdhash, completely unsigned? Code has to be at least ad-hoc signed.
Then, if I launch the application to sign the sub-framework with the following script:
pushd ${TARGET_BUILD_DIR}/${PRODUCT_NAME}.app/Frameworks/Umbrella.framework/Frameworks
for EACH in *.framework; do
echo "-- signing ${EACH}"
/usr/bin/codesign --force --deep --sign "${EXPANDED_CODE_SIGN_IDENTITY}" --entitlements "${TARGET_TEMP_DIR}/${PRODUCT_NAME}.app.xcent" --timestamp=none $EACH
done
popd
I get this error:
/Users/xxx/Library/Developer/Xcode/DerivedData/sdkInteTest-bbfpzsxuhjomfmaumywyncnbwbla/Build/Intermediates.noindex/sdkInteTest.build/Debug-iphoneos/sdkInteTest.build/Script-F9547ACC224017BF0030EA0B.sh: line 3: pushd: /Users/xxx/Library/Developer/Xcode/DerivedData/sdkInteTest-bbfpzsxuhjomfmaumywyncnbwbla/Build/Products/Debug-iphoneos/sdkInteTest.app/Frameworks/Umbrella.framework/Frameworks: No such file or directory
-- signing *.framework
*.framework: No such file or directory
/Users/xxx/Library/Developer/Xcode/DerivedData/sdkInteTest-bbfpzsxuhjomfmaumywyncnbwbla/Build/Intermediates.noindex/sdkInteTest.build/Debug-iphoneos/sdkInteTest.build/Script-F9547ACC224017BF0030EA0B.sh: line 8: popd: directory stack empty
The Solution
The problem is that the script was starting when the pod wasn't already attached.
The script should be run when all the pod jobs are done.
I wrote a full guide to build an iOS Umbrella framework!
The solution I found is the following:
1) Step one:
In the podfile of the integration project (not the umbrella project) add the following line of code where you add dependencies:
script_phase :name => 'Sign', :script => './sign.sh'
like this:
target 'yourTarget' do
# Pods for sdkInteTest
#your pods goes here
script_phase :name => 'Sign', :script => './sign.sh'
end
2) Step two:
Than in the terminal at the root of your test Integration project:
In the terminal type:
touch sign.sh
chmod 777 sign.sh
open sign.sh
And in the script file add this code:
echo "Signing subframeworks"
pushd "${TARGET_BUILD_DIR}"/"${PRODUCT_NAME}".app/Frameworks/YOURFRAMEWORKNAME.framework/Frameworks
for EACH in *.framework; do
echo "-- signing ${EACH}"
/usr/bin/codesign --force --deep --sign "${EXPANDED_CODE_SIGN_IDENTITY}" --entitlements "${TARGET_TEMP_DIR}/${PRODUCT_NAME}.app.xcent" --timestamp=none $EACH
done
popd
echo "BUILD DIR ${TARGET_BUILD_DIR}"
remember to rename your framework name.
In this way you are telling to CocoaPods to run a script phase after the pod installation.
Unfortunately this is a "client" solution, I tried to find a solution to apply at framework level without any luck.

Use different GoogleService-Info.plist for different build schemes

I am using a build scheme for prod and one for staging (with 2 different bundle identifiers) and I am trying to use a separate GoogleService-Info.plist for each scheme.
Is there any way to manually select the plist file to use when initialising GCM (and goole login)? Or is its possible to avoid using the plist and do the setup manually?
Thanks!
Details
Tested on:
Xcode 9.2
Xcode 10.2 (10E125)
Xcode 11.0 (11A420a)
Solution
Create folder with all your Google.plist files (with different names) in project
Add run script
Do not forget to change PATH_TO_GOOGLE_PLISTS value
Code
PATH_TO_GOOGLE_PLISTS="${PROJECT_DIR}/SM2/Application/Firebase"
case "${CONFIGURATION}" in
"Debug_Staging" | "AdHoc_Staging" )
cp -r "$PATH_TO_GOOGLE_PLISTS/GoogleService-Info-dev.plist" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist" ;;
"Debug_Production" | "AdHoc_Production" | "Distribution" | "Test_Production" )
cp -r "$PATH_TO_GOOGLE_PLISTS/GoogleService-Info-prod.plist" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist" ;;
*)
;;
esac
Build schemes names
#inidona 's answer worked for me. After I converted it to Swift
for Swift 2.3:
let filePath = NSBundle.mainBundle().pathForResource("GoogleService-Info", ofType: "plist")
let options = FIROptions(contentsOfFile: filePath)
FIRApp.configureWithOptions(options)
for Swift 3.0:
let filePath = Bundle.main.path(forResource: "GoogleService-Info", ofType: "plist")!
let options = FIROptions(contentsOfFile: filePath)
FIRApp.configure(with: options)
for Swift 4.0:
let filePath = Bundle.main.path(forResource: "GoogleService-Info", ofType: "plist")!
let options = FirebaseOptions(contentsOfFile: filePath)
FirebaseApp.configure(options: options!)
If the GoogleService-Info.plist has a different name it will affect your analytics results. Firebase will warn you about this. For this reason, none of these runtime-solutions will provide the best analytics results.
There are two solutions that won't mess with Analytics.
Use a different target with each scheme and associate each version of GoogleService-Info.plist with its own target. See Target Membership in the File inspector on the right hand side in Xcode. For further info See this question.
Use a build phase script to copy the correct version of GoogleService-Info.plist into the build directory. I use a different bundle ID for staging and production. This enables me to have both versions of the app installed in parallel. It also means with the script below I can name my different GoogleService-Info.plist files with the bundle ID. For example:
GoogleService-Info-com.example.app.plist
GoogleService-Info-com.example.app.staging.plist
Build Phase Script
PATH_TO_CONFIG=$SRCROOT/Config/GoogleService-Info-$PRODUCT_BUNDLE_IDENTIFIER.plist
FILENAME_IN_BUNDLE=GoogleService-Info.plist
BUILD_APP_DIR=${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app
echo cp $PATH_TO_CONFIG "$BUILD_APP_DIR/$FILENAME_IN_BUNDLE"
cp $PATH_TO_CONFIG "$BUILD_APP_DIR/$FILENAME_IN_BUNDLE"
Note: You will have to change PATH_TO_CONFIG to suit you setup.
Check this article: https://medium.com/#brunolemos/how-to-setup-a-different-firebase-project-for-debug-and-release-environments-157b40512164
On Xcode, create two directories inside your project: Debug and Release. Put each GoogleService-Info.plist file there.
On AppDelegate.m, inside the didFinishLaunchingWithOptions method, put the code:
Objective-C
NSString *filePath;
#ifdef DEBUG
NSLog(#"[FIREBASE] Development mode.");
filePath = [[NSBundle mainBundle] pathForResource:#"GoogleService-Info" ofType:#"plist" inDirectory:#"Debug"];
#else
NSLog(#"[FIREBASE] Production mode.");
filePath = [[NSBundle mainBundle] pathForResource:#"GoogleService-Info" ofType:#"plist" inDirectory:#"Release"];
#endif
FIROptions *options = [[FIROptions alloc] initWithContentsOfFile:filePath];
[FIRApp configureWithOptions:options];
Swift 4
var filePath:String!
#if DEBUG
print("[FIREBASE] Development mode.")
filePath = Bundle.main.path(forResource: "GoogleService-Info", ofType: "plist", inDirectory: "Debug")
#else
print("[FIREBASE] Production mode.")
filePath = Bundle.main.path(forResource: "GoogleService-Info", ofType: "plist", inDirectory: "Release")
#endif
let options = FirebaseOptions.init(contentsOfFile: filePath)!
FirebaseApp.configure(options: options)
Drag & drop both Debug and Release folders to the Build Phases > Copy Bundle Resources:
That's it :)
I think you can use this way to configure your GoogleService-Info.plist dynamicly and use different names for different bundle identifiers.
ciao
Andreas
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"GoogleService-Info" ofType:#"plist"];
FIROptions *options = [[FIROptions alloc] initWithContentsOfFile:filePath];
[FIRApp configureWithOptions:options];
I noticed that google expects the filename to be GoogleServiceInfo.plist in the code:
* The method |configureWithError:| will read from the file GoogleServices-Info.plist bundled with
* your app target for the keys to configure each individual API. To generate your
* GoogleServices-Info.plist, please go to https://developers.google.com/mobile/add
*
* #see GGLContext (Analytics)
* #see GGLContext (SignIn)
*/
#interface GGLContext : NSObject
the key phrase is this one
read from the file GoogleServices-Info.plist bundled with your app target
So I simply copied the same file and put it into different directories, and bounded it to different targets:
This answer is very much inspired by #abbood's answer, but a bit more specific on how to do it.
For each of your targets, e.g. dev, stg, prod:
Download the corresponding GoogleService-Info.plist to a separate folder named after your target
In Xcode, right-click your app folder and choose Add files to "your app"
Select the folder containing the target's GoogleService-Info.plist, make sure Copy items if needed and Create groups are selected, check only the corresponding target in the list of targets, and press Add
That's it. Now you should have something similar to this structure
When you build a target, the correct GoogleService-Info.plist will be used.
Late but I think I must post this answer to help new developers, I found a very good article that resole my problem and I promise it can help you as well :)
Check this article that resolve your problem as well.
Step 1:
Copy the GoogleService-Info.plist corresponding to your Firebase development environment into the Dev directory. Similarly, copy the GoogleService-Info.plist corresponding to your Firebase production environment in the Prod directory. Make sure to uncheck “Copy items if needed” and all targets under “Add to targets”.
Step 2:
In the Xcode project navigator, select the app target. Switch to the Build Phases tab at the top, then add a New Run Script Phase. Name the phase “Setup Firebase Environment GoogleService-Info.plist”, or something to that effect, and place it before the “Copy Bundle Resources” step.
Step 3:
Implement a shell script that will copy the appropriate GoogleService-Info.plist into the app bundle based on the build configuration. Copy and paste the following shell script into the run script phase you just created:
# Name of the resource we're selectively copying
GOOGLESERVICE_INFO_PLIST=GoogleService-Info.plist
# Get references to dev and prod versions of the GoogleService-Info.plist
# NOTE: These should only live on the file system and should NOT be part of the target (since we'll be adding them to the target manually)
GOOGLESERVICE_INFO_DEV=${PROJECT_DIR}/${TARGET_NAME}/Firebase/Dev/${GOOGLESERVICE_INFO_PLIST}
GOOGLESERVICE_INFO_PROD=${PROJECT_DIR}/${TARGET_NAME}/Firebase/Prod/${GOOGLESERVICE_INFO_PLIST}
# Make sure the dev version of GoogleService-Info.plist exists
echo "Looking for ${GOOGLESERVICE_INFO_PLIST} in ${GOOGLESERVICE_INFO_DEV}"
if [ ! -f $GOOGLESERVICE_INFO_DEV ]
then
echo "No Development GoogleService-Info.plist found. Please ensure it's in the proper directory."
exit 1
fi
# Make sure the prod version of GoogleService-Info.plist exists
echo "Looking for ${GOOGLESERVICE_INFO_PLIST} in ${GOOGLESERVICE_INFO_PROD}"
if [ ! -f $GOOGLESERVICE_INFO_PROD ]
then
echo "No Production GoogleService-Info.plist found. Please ensure it's in the proper directory."
exit 1
fi
# Get a reference to the destination location for the GoogleService-Info.plist
PLIST_DESTINATION=${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app
echo "Will copy ${GOOGLESERVICE_INFO_PLIST} to final destination: ${PLIST_DESTINATION}"
# Copy over the prod GoogleService-Info.plist for Release builds
if [ "${CONFIGURATION}" == "Release" ]
then
echo "Using ${GOOGLESERVICE_INFO_PROD}"
cp "${GOOGLESERVICE_INFO_PROD}" "${PLIST_DESTINATION}"
else
echo "Using ${GOOGLESERVICE_INFO_DEV}"
cp "${GOOGLESERVICE_INFO_DEV}" "${PLIST_DESTINATION}"
fi
I found that in case of single target the only 100% viable way is to copy plist corresponding to build configuration during the build; but such answers here differ in details of how to do it, and no one was convenient enough for me. My answer is based on answer by #KnightFighter and this article on Medium.
Firstly add all different plists to project with different names (they must not be added to target as resources):
Next create user-defined build setting, where you can assign specific plist to each build configuration:
Finally add "Run script" phase with code:
GOOGLE_SERVICE_INFO_PLIST_SOURCE=${PROJECT_DIR}/${TARGET_NAME}/${GOOGLE_SERVICE_INFO_PLIST_FILENAME}
if [ ! -f $GOOGLE_SERVICE_INFO_PLIST_SOURCE ]
then
echo "${GOOGLE_SERVICE_INFO_PLIST_SOURCE} not found."
exit 1
fi
GOOGLE_SERVICE_INFO_PLIST_DESTINATION="${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist"
cp "${GOOGLE_SERVICE_INFO_PLIST_SOURCE}" "${GOOGLE_SERVICE_INFO_PLIST_DESTINATION}"
I think such way has some advantages:
no need to have folders hierarchy to store plists;
no need to duplicate file if single plist is used for several configurations;
it's easier to change filename in build settings than edit script if you need to add configuration or reassign plists; especially for non-programmers (i e build manager).
Let's suppose we have two configurations set, develop and production. You have to make two things:
Rename both plists to conform to given configuration:
GoogleService-Info-develop.plist
GoogleService-Info-production.plist
Add a run script which copies the correct plist for selected configuration:
FIREBASE_PLIST_PATH="${PROJECT_DIR}/App/Resources/Plists/GoogleService-Info-${CONFIGURATION}.plist"
echo "Firebase plist path: ${FIREBASE_PLIST_PATH}"
cp -r ${FIREBASE_PLIST_PATH} "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist"
A run script needs to be positioned before FirebaseCrashlytics script.
You you can init firebase as you did before for single scheme: FirebaseApp.configure()
You cannot avoid to use the plist with Firebase. The best solution I found so far for you it would be to add both files and name it
GoogleService-Info_stage.plist
and
GoogleService-Info_prod.plist
Then from your code you can call the correct file. This way won't crash your app if you don't have the file. Just replace FILENAME with GoogleService-Info_prod or GoogleService-Info_stage.
if let configFile = Bundle.main.path(forResource: "FILENAME", ofType: "plist"),
let options = FirebaseOptions(contentsOfFile: configFile)
{
FirebaseApp.configure(options: options)
}
Here's my version of #Essam's solution.
Generate a GoogleServices version for the default scheme
(Google-Services.plist) with the default identifier
Generate a second GoogleServices version
for the variant scheme (Google-Services-debug.plist) with the correct identifier
Add both to
the root of your project (where it tells you to in their guide)
Add this code where you'd add configure:
let bundleID = Bundle.main.bundleIdentifier
if (bundleID!.contains("debug")) {
let resource: String = "GoogleService-Info-debug"
let filePath = Bundle.main.path(forResource: resource, ofType: "plist")!
let options = FirebaseOptions(contentsOfFile: filePath)
FirebaseApp.configure(options: options!)
} else {
FirebaseApp.configure()
}
I solved this by this:
#if STAGING
if let filePath = Bundle.main.path(forResource: "GoogleService-Info-Dev", ofType: "plist"),
let options = FirebaseOptions(contentsOfFile: filePath) {
FirebaseApp.configure(options: options)
} else {
fatalError("GoogleService-Info-Dev.plist is missing!")
}
#else
if let filePath = Bundle.main.path(forResource: "GoogleService-Info", ofType: "plist"),
let options = FirebaseOptions(contentsOfFile: filePath) {
FirebaseApp.configure(options: options)
} else {
fatalError("GoogleService-Info.plist is missing!")
}
#endif
So I have pondered the same question and using some ideas from earlier posts, some of which publish apps with GoogleServices-Info.plist for all environments in all apps and that is a bit of a concern.
I have come up with an extensible solution that copies the GoogleSerives-Info.plist file at build time. Further more this approach can support as many environments as you like with the ability to customise and follows a simple convention, making it easy to manage.
First and foremost i have three environments, debug (For running in simulator and device whist debugging and actively cutting code), staging (for deployment to test flight) and release for production.
Step one is to create your configuration(s):
Select "Product" -> "Scheme" -> "Edit Scheme" and duplicate/create new as required. Go through each Scheme and assign its respective configuration from the
"Build Configuration" drop down in each of the categories:
I go a step further and uncheck "run" for Schemes that need to be distributed i.e. release and staging, and conversely uncheck "archive" for debug. You should do what makes sense for you.
Under build phases add the following run scrip (CONFIGURATIONS_FOLDER variable can be customised as desired - just ensure you use the same folder name in the next step):
# Get a reference to the folder which contains the configuration subfolders.
CONFIGURATIONS_FOLDER=Firebase
# Get a refernce to the filename of a 'GoogleService-Info.plist' file.
GOOGLESERVICE_INFO_PLIST=GoogleService-Info.plist
# Get a reference to the 'GoogleService-Info.plist' for the current configuration.
GOOGLESERVICE_INFO_PLIST_LOCATION=${PROJECT_DIR}/${TARGET_NAME}/${CONFIGURATIONS_FOLDER}/${CONFIGURATION}/${GOOGLESERVICE_INFO_PLIST}
# Check if 'GoogleService-Info.plist' file for current configuration exist.
if [ ! -f $GOOGLESERVICE_INFO_PLIST_LOCATION ]
then
echo "No '${GOOGLESERVICE_INFO_PLIST}' file found for the configuration '${CONFIGURATION}' in the configuration directory '${PROJECT_DIR}/${TARGET_NAME}/${CONFIGURATIONS_FOLDER}/${CONFIGURATION}'."
exit 1
fi
# Get a reference to the destination location for the GoogleService-Info.plist.
GOOGLESERVICE_INFO_PLIST_DESTINATION=${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app
# Copy 'GoogleService-Info.plist' for current configution to destination.
cp "${GOOGLESERVICE_INFO_PLIST_LOCATION}" "${GOOGLESERVICE_INFO_PLIST_DESTINATION}"
echo "Successfully coppied the '${GOOGLESERVICE_INFO_PLIST}' file for the '${CONFIGURATION}' configuration from '${GOOGLESERVICE_INFO_PLIST_LOCATION}' to '${GOOGLESERVICE_INFO_PLIST_DESTINATION}'."
In your chosen configurations folder ("Firebase" in the above example) nest folders for each configuration named exactly the same as its respective configuration (case sensitive), inside of which place the respective GoogleServices-Info.plist files like so:
Last but not least, i also like to ensure that a root level GoogleServices-Info.plist is not added into the project by accident so I add the following to my .gitignore.
# Ignore project level GoogleService-Info.plist
/[Project Name]/GoogleService-Info.plist
This is my solution!
NSString *filePath;
if([self isProduction]){
filePath = [[NSBundle mainBundle] pathForResource:#"GoogleService-Info" ofType:#"plist"];
}else{
filePath = [[NSBundle mainBundle] pathForResource:#"GoogleService-Info-Sandbox" ofType:#"plist"];
}
FIROptions *options = [[FIROptions alloc] initWithContentsOfFile:filePath];
[FIRApp configureWithOptions:options];
And That's it!
I think it is not possible to achieve without using the GoogleService-Info.plist. Because before you can begin integrating your iOS app with the Google Sign-In components, you must download the dependencies and configure your Xcode project.
And this process shows that GoogleService-Info.plist has a big factor on it.
So the solutions and idea here in this SO question can help you with your problem. Just moved the main copy of the GoogleService-Info plist out of the app into 2 separate folders, then used the Build Phases "Copy Files" on each target to import the target specific plist into the Resources folder.
Also check this SO question, it might give you more information/idea to your problem.
If some of you fall into an error and Xcode complains
"Multiple commands produce GoogleService-Info.plist"
after applying #Knight Fighter response, you may want to:
Check Build Phases > Copy Bundle Resources
Filter for files named GoogleService-Info.plist
Remove any references you have to it, since
it's already being copied through the script.
Here's how to do it in Xamarin C#:
string plistPath = NSBundle.MainBundle.PathForResource ("GoogleService-Info", "plist");
Options options = new Options (plistPath);
App.Configure (options);
Remember to include the Firebase namespace:
using Firebase.Analytics;
With Xcode 9.2, I have needed files for both targets to be named "googleServiceInfo.plist" but placed in different directories, with the directory/file for each target specified in "Build Phases", "Copy Bundle Resources".
The above was not my preferred solution, but I had previously tried using different filenames along the lines of #inidona's answer, converted to Swift 4:
let filePath = Bundle.main.path(forResource: "googleServiceInfo-Pro", ofType: "plist")!
let options = FirebaseOptions(contentsOfFile: filePath)
FirebaseApp.configure(options: options!)
Unfortunately, this did not correct the Firebase error messages. In this question: Firebase iOS SDK - Using configuration file other than GoogleService-Info.plist generates console warning the original poster seems to have fixed by updating the Firebase Pod but I have not confirmed this.
#Vasily Bodnarchuk answer worked for me. The only thing that you need to pay attention is that the scripts in Xcode have a precise order, so you need to put this script as first one, before the scripts with
${PODS_ROOT}/FirebaseCrashlytics/run
and
"${PODS_ROOT}/FirebaseCrashlytics/upload-symbols" -gsp "${PROJECT_DIR}/<yourapp>/Configuration Files/GoogleService-Info-dev.plist" -p ios "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}"
For those who want to do it in Fastlane.
You can use the file manager plugin for fastlane (fastlane-plugin-file_manager), to run a simple copy command.
Add a GoogleService-info-app.plist to your xcode project the standard way so it's linked properly.
Use copy files to overwrite this linked file with the files you want in your build / beta lane.
copy_files(source: "firebase/GoogleService-Info-" + ENV["APP_IDENTIFIER"] + ".plist", destination: "GoogleService-Info.plist")
though late to the party, I have a solution implemented for this.
At first names your plists like below:
GoogleService-Info-target1
GoogleService-Info-target2
GoogleService-Info-target3
And then add the below Script in the Build Phases Tabs of each target by adding a new runs script phase:
PATH_TO_PLISTS="${PROJECT_DIR}/${PROJECT_NAME}/(Folder Name containing
all the plists)" case "${TARGET_NAME}" in
"target1 name" ) cp -r
"$PATH_TO_PLISTS/GoogleService-Info-target1.plist"
"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist"
;;
"target2 name" ) cp -r
"$PATH_TO_PLISTS/GoogleService-Info-target2.plist"
"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist"
;;
"target3 name" ) cp -r
"$PATH_TO_PLISTS/GoogleService-Info-target3.plist"
"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist"
;;
*) ;; esac
!Finally it working for macOS App.
Thanks #vasily-bodnarchuk for his solution for iOS app.But for macOS app it needs little extra modification in script file.
Just append the designated resources directory for macOS "Contents\Resources". Please check for detail Copy Bundle Resources
Code
PATH_TO_GOOGLE_PLISTS="${PROJECT_DIR}/SM2/Application/Firebase"
case "${CONFIGURATION}" in
"Debug_Staging" | "AdHoc_Staging" )
cp -r "$PATH_TO_GOOGLE_PLISTS/GoogleService-Info-dev.plist" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/Contents/Resources/GoogleService-Info.plist" ;;
"Debug_Poduction" | "AdHoc_Poduction" | "Distribution" | "Test_Poduction" )
cp -r "$PATH_TO_GOOGLE_PLISTS/GoogleService-Info-prod.plist" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/Contents/Resources/GoogleService-Info.plist" ;;
*)
;;
esac
For multiple schemes like Stage, QA, UAT and PROD with one target below script worked for me.
Also i have maintained .xcconfig files with their configuration name
# Get a reference to the folder which contains the configuration
subfolders.
CONFIGURATIONS_FOLDER=Firebase
# Get a refernce to the filename of a 'GoogleService-Info.plist' file.
GOOGLESERVICE_INFO_PLIST=GoogleService-Info.plist
# Get a reference to the 'GoogleService-Info.plist' for the current
configuration. ENV_NAME name is the folder name(Dev,QA,Uat,Prod)
which i have in my .xcconfig file for each environment.
GOOGLESERVICE_INFO_PLIST_LOCATION=${PROJECT_DIR}/${TARGET_NAME}
/${CONFIGURATIONS_FOLDER}/${ENV_NAME}/${GOOGLESERVICE_INFO_PLIST}
# Check if 'GoogleService-Info.plist' file for current configuration
exist.
if [ ! -f $GOOGLESERVICE_INFO_PLIST_LOCATION ]
then
echo "No '${GOOGLESERVICE_INFO_PLIST}' file found for the
configuration '${CONFIGURATION}' in the configuration directory
'${PROJECT_DIR}/${TARGET_NAME}/${CONFIGURATIONS_FOLDER}/
${CONFIGURATION}'."
exit 1
fi
# Get a reference to the destination location for the GoogleService-
Info.plist.
GOOGLESERVICE_INFO_PLIST_DESTINATION=${BUILT_PRODUCTS_DIR}
/${PRODUCT_NAME}.app
# Copy 'GoogleService-Info.plist' for current configution to
destination.
cp "${GOOGLESERVICE_INFO_PLIST_LOCATION}"
"${GOOGLESERVICE_INFO_PLIST_DESTINATION}"
echo "Successfully coppied the '${GOOGLESERVICE_INFO_PLIST}' file
for the '${CONFIGURATION}' configuration from
'${GOOGLESERVICE_INFO_PLIST_LOCATION}' to
'${GOOGLESERVICE_INFO_PLIST_DESTINATION}'."

Resources