The steps that worked for me:
Add Firebase to app extension
Set up shared container via App Group
(or registered URL scheme etc., not going in details here about those)
Sync login information (or any other data) using UserDefaults
Step 1. Add Firebase to app extension
An app extension is treated as a separate app by Firebase, therefore one needs to it to the main Firebase project.
Steps:
- Add new iOS app to your existing Firebase project
- Drag the new
GoogleService-Info.plist
into your extension in Xcode
- Add new target to your
Podfile
- Install dependencies (
pod install
)
- Configure the Firebase application object in your extension
See detailed steps in this SO answer.
Step 2. Set up shared container via App Group
Apps in a Firebase project are isolated from each other. For example, users, who logged in to the project from the app extension, will have to log in the containing app as well. Checking Auth.auth().currentUser
will only yield results for the specific context.
The official Apple guide (App Extension Programming Guide: Sharing Data with Your Containing App) shows how to do it, and gives a good explanation with illustrations.
Steps:
Create new App Group and configure iOS app to use it
Enable "App Groups" capability in Xcode for both targets (i.e., extension and containing app)
For example, our main app is "Access News":
The share extension is "Access-News-Uploader":
Step 3. Sync login information (or any other data) using UserDefaults
Make sure to save the user ID in defaults! The users in Firebase are handled in the project level which makes it possible for the users to sign in via any app in the project (e.g., one for the containing app and one for the extension), but these states are only saved for the actual app in use. For example, if the user signs in the share extension and opens the containing app, if the containing app calls Auth.auth().currentUser.uid
at any point it will probably yield nil
.
To share data using UserDefaults
singleton and App Groups follow the steps in any class where you need them:
let defaults = UserDefaults.init(suiteName: "group.your-app-group-id")!
Set default values using one of the UserDefaults.set(...)
functions
Query the the values with a specific UserDefaults
getter
Example
When our project starts up, the containing app's (Access News) root view controller (NVC) checks the "user-logged-in"
bool UserDefaults
value (and not Auth.auth().currentUser
as that would only show the containing apps Firebase app status).
(Could've just saved the user ID, and check if it is present, instead of using both a Bool
and String
key.)
// NVC.swift
import UIKit
import FirebaseAuth
class NVC: UINavigationController {
/* Step 1 */
let defaults = UserDefaults.init(suiteName: "group.org.societyfortheblind.access-news-reader-ag")!
/**********/
override func viewDidLoad() {
super.viewDidLoad()
/* Step 3 */
if self.defaults.bool(forKey: "user-logged-in") == false {
/**********/
let storyboard = UIStoryboard(name: "Main", bundle: .main)
let loginViewController = storyboard.instantiateViewController(withIdentifier: "LoginViewController")
loginViewController.navigationItem.hidesBackButton = true
self.pushViewController(loginViewController, animated: false)
}
}
If no user is logged in, LoginViewController
is loaded that sets the key to true
on successful login. (It is set to false
on logout in SessionStartViewController
.)
// ViewController.swift
import UIKit
import FirebaseAuth
class LoginViewController: UIViewController {
/* Step 1 */
let defaults = UserDefaults.init(suiteName: "group.org.societyfortheblind.access-news-reader-ag")!
/**********/
// ...
@IBAction func tapSignInButton(_ sender: Any) {
// ...
Auth.auth().signIn(withEmail: username.text!, password: password.text!) {
(user, error) in
if error != nil {
// ...
} else {
/* Step 2 */
self.defaults.set(true, forKey: "user-logged-in")
// !!!
self.defaults.set(Auth.auth().currentUser.uid, forKey: "user-id")
/**********/
self.navigationController?.popViewController(animated: false)
}
}
}
}
In the app extension the key is checked in the main navigation controller, and if no user is logged it, it would load LoginViewController
from the containing app.
// UploaderNavigationViewController.swift
import UIKit
import Firebase
class UploaderNavigationViewController: UINavigationController {
/* Step 1 */
let defaults = UserDefaults.init(suiteName: "group.org.societyfortheblind.access-news-reader-ag")!
/**********/
override func viewDidLoad() {
super.viewDidLoad()
if FirebaseApp.app() == nil {
FirebaseApp.configure()
}
/* Step 3 */
if self.defaults.bool(forKey: "user-logged-in") == false {
/**********/
let storyboard = UIStoryboard(name: "Main", bundle: .main)
let loginViewController = storyboard.instantiateViewController(withIdentifier: "LoginViewController")
loginViewController.navigationItem.hidesBackButton = true
self.pushViewController(loginViewController, animated: false)
}
}
Here is our project at the commit when this has been set up.