How to share an object between AppDelegate and SceneDelegate - ios

I am already using AppDelegate for registering with APNs, but since it does not support the applicationWillResignActive() and applicationWillEnterForeground() functions which handled by the SceneDelegate as explained in here.
I want both delegates to have the access to my client instance. Currently, the AppDelegate instantiates the client as its property and then it is passed to the scene as an environmentObject:
import SwiftUI
#main
struct app1App: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
MainView().environmentObject(appDelegate.client)
}
}
}
class AppDelegate: NSObject, UIApplicationDelegate {
let client : Client = Client()
// other Notifications related functions
}
When I declare my client in views as:
#EnvironmentObject var client: Client
then the views have the access to the client's methods, however, the same approach does not work when I declare it in the SceneDelegate:
class SceneDelegate: NSObject, UIWindowSceneDelegate {
#EnvironmentObject var client: Client
func sceneWillEnterForeground(_ scene: UIScene) {
self.client.disconnect() // This fails in runtime!!!
}
}
The error says it is expecting an observable object, but my object is environmentObject:
Thread 1: Fatal error: No ObservableObject of type Client found. A View.environmentObject(_:) for Client may be missing as an ancestor of this view.
What's the proper way to provide the access to the client by the functions from the SceneDeleagte?

Just need
help with sharing the client between the two delegates - any
suggestions?
If it is app-wide to be shared then the simplest is to make it global
let client : Client = Client() // << create here
class AppDelegate: NSObject, UIApplicationDelegate {
// access `client` here directly
// other Notifications related functions
}
class SceneDelegate: NSObject, UIWindowSceneDelegate {
func sceneWillEnterForeground(_ scene: UIScene) {
// access `client` here directly
client.disconnect() // This fails in runtime!!!
}
}

Related

NSInternalInconsistencyException: Firebase crashes SwiftUI app on launch

I am trying to add Firebase auth via Apple Sign-in and have got this error:
crashed due to an uncaught exception `NSInternalInconsistencyException`. Reason: The default FirebaseApp instance must be configured before the default Authinstance can be initialized. One way to ensure this is to call `FirebaseApp.configure()` in the App Delegate's `application(_:didFinishLaunchingWithOptions:)` (or the `#main` struct's initializer in SwiftUI)
This is how I launch Firebase:
import Firebase
import SwiftUI
class AppDelegate: NSObject, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
FirebaseApp.configure()
return true
}
}
I did not install the Firebase package directly, but instead added this package. which I believe added Firebase as one of its dependency. I have checked other solutions but they all rely on CocoaPods while I'm using the Swift Package Manager. Not sure how I can resolve this error.
App calling AppDelegate:
import FirebaseService
import SwiftUI
#main
struct SomeApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
#State private var authState = AuthState()
var body: some Scene {
WindowGroup {
switch authState.value {
case .undefined:
ProgressView()
case .authenticated:
ContentView()
case .notAuthenticated:
AuthView()
}
}
}
}
This error message indicates that another part of your app tries to access Firebase Auth before Firebase is initialised in your your app delegate.
Looking at the code of Rebeloper's package which you're using, we can see that it calls Auth.auth().addStateDidChangeListener in line 16 of AuthListener. Since you create an instance of AuthState (another class from Rebeloper's library), this is executed before Firebase is initialised.
Instead of using an undocumented third party library, I would recommend implementing this yourself - especially since it's only a few lines of code.
To see how this is done, check out Getting started with Firebase Auth for Apple platforms - Firebase Fundamentals, which not only explains Firebase Auth and how to handle authentication state in detail, but also includes the corresponding source code.
As an added bonus, this contains a beautiful signup/login form:
For reference, here is the source code for registering an authentication state listener in an ObserableObject:
#MainActor
class AuthenticationViewModel: ObservableObject {
#Published var email = ""
#Published var password = ""
#Published var confirmPassword = ""
#Published var flow: AuthenticationFlow = .login
#Published var isValid = false
#Published var authenticationState: AuthenticationState = .unauthenticated
#Published var errorMessage = ""
#Published var user: User?
#Published var displayName = ""
init() {
registerAuthStateHandler()
// ...
}
private var authStateHandler: AuthStateDidChangeListenerHandle?
func registerAuthStateHandler() {
if authStateHandler == nil {
authStateHandler = Auth.auth().addStateDidChangeListener { auth, user in
self.user = user
self.authenticationState = user == nil ? .unauthenticated : .authenticated
self.displayName = user?.email ?? ""
}
}
}
// ...
}

SceneDelegate.swift deprecated/changed architecture? [duplicate]

Now that AppDelegate and SceneDelegate are removed from SwiftUI, where do I put the code that I used to have in SceneDelegate and AppDelegate, Firebase config for ex?
So I have this code currently in my AppDelegate:
Where should I put this code now?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
FirebaseConfiguration.shared.setLoggerLevel(.min)
FirebaseApp.configure()
return true
}
Here is a solution for SwiftUI life-cycle. Tested with Xcode 12b / iOS 14
import SwiftUI
import UIKit
// no changes in your AppDelegate class
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
print(">> your code here !!")
return true
}
}
#main
struct Testing_SwiftUI2App: App {
// inject into SwiftUI life-cycle via adaptor !!!
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Overriding the initializer in your App also works:
import SwiftUI
import Firebase
#main
struct BookSpineApp: App {
init() {
FirebaseApp.configure()
}
var body: some Scene {
WindowGroup {
BooksListView()
}
}
}
Find a more detailed write-up here:
The Ultimate Guide to the SwiftUI 2 Application Life Cycle
Firebase and the new SwiftUI 2 Application Life Cycle
You should not put that kind of codes in the app delegate at all or you will end up facing the Massive App Delegate. Instead, you should consider refactoring your code to more meaningful pieces and then put the right part in the right place. For this case, the only thing you need is to be sure that the code is executing those functions once the app is ready and only once. So the init method could be great:
#main
struct MyApp: App {
init() {
setupFirebase()
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
private extension MyApp {
func setupFirebase() {
FirebaseConfiguration.shared.setLoggerLevel(.min)
FirebaseApp.configure()
}
}
AppDelegate ?
You can have your own custom class and assign it as the delegate. But note that it will not work for events that happen before assignment. For example:
class CustomDelegate: NSObject, UIApplicationDelegate {
static let Shared = CustomDelegate()
}
And later:
UIApplication.shared.delegate = CustomDelegate.Shared
Observing For Notifications
Most of AppDelegate methods are actually observing on notifications that you can observe manually instead of defining a new class. For example:
NotificationCenter.default.addObserver(
self,
selector: #selector(<#T###objc method#>),
name: UIApplication.didBecomeActiveNotification,
object: nil
)
Native AppDelegate Wrapper
You can directly inject app delegate into the #main struct:
#UIApplicationDelegateAdaptor(CustomDelegate.self) var appDelegate
Note: Using AppDelegate
Remember that adding AppDelegate means that you are killing default multiplatform support and you have to check for platform manually.
You can also use the new ScenePhase for certain code that the AppDelegate and SceneDelegate had. Like going to the background or becoming active. From
struct PodcastScene: Scene {
#Environment(\.scenePhase) private var phase
var body: some Scene {
WindowGroup {
TabView {
LibraryView()
DiscoverView()
SearchView()
}
}
.onChange(of: phase) { newPhase in
switch newPhase {
case .active:
// App became active
case .inactive:
// App became inactive
case .background:
// App is running in the background
#unknown default:
// Fallback for future cases
}
}
}
}
Example credit: https://wwdcbysundell.com/2020/building-entire-apps-with-swiftui/
Note the method below will stop cross platform support so should only be used if you are planning on building for iOS only.
It should also be noted that this doesn’t use the SwiftUI lifecycle method, instead it allows you to return to the UIKit lifecycle method.
You can still have an AppDelegate and a SceneDelegate when you create a SwiftUI app in Xcode 12-beta.
You just need to make sure that you have chosen the correct option for the Life Cycle when you create your app.
Make sure you choose UIKit App Delegate for the Life Cycle and you will get an AppDelegate and a SceneDelegate
I would also advise in using the main App's init method for this one, as it seems safe to use (any objections?).
What I usually do, that might be useful to share, is to have a couple of utility types, combined with the Builder pattern.
/// An abstraction for a predefined set of functionality,
/// aimed to be ran once, at app startup.
protocol StartupProcess {
func run()
}
/// A convenience type used for running StartupProcesses.
/// Uses the Builder pattern for some coding eye candy.
final class StartupProcessService {
init() { }
/// Executes the passed-in StartupProcess by running it's "run()" method.
/// - Parameter process: A StartupProcess instance, to be initiated.
/// - Returns: Returns "self", as a means to chain invocations of StartupProcess instances.
#discardableResult
func execute(process: any StartupProcess) -> StartupProcessService {
process.run()
return self
}
}
and then we have some processes
struct CrashlyticsProcess: StartupProcess {
func run() {
// Do stuff, like SDK initialization, etc.
}
}
struct FirebaseProcess: StartupProcess {
func run() {
// Do stuff, like SDK initialization, etc.
}
}
struct AppearanceCustomizationProcess: StartupProcess {
func run() {
// Do stuff, like SDK initialization, etc.
}
}
and finally, running them
#main
struct TheApp: App {
init() {
initiateStartupProcesses()
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
private extension TheApp {
func initiateStartupProcesses() {
StartupProcessService()
.execute(process: ExampleProcess())
.execute(process: FirebaseProcess())
.execute(process: AppearanceCustomizationProcess)
}
}
Seems quite nice and super clean.
I see a lot of solutions where init gets used as didFinishLaunching. However, didFinishLaunching gets called AFTER init of the App struct.
Solution 1
Use the init of the View that is created in the App struct. When the body of the App struct gets called, didFinishLaunching just happened.
#main
struct MyApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
#ViewBuilder
var body: some Scene {
WindowGroup {
MainView(appDelegate: appDelegate)
}
}
}
struct MainView: View {
init(appDelegate: AppDelegate) {
// at this point `didFinishLaunching` is completed
setup()
}
}
Solution 2
We can create a block to notify us when didFinishLaunching gets called. This allows to keep more code in SwiftUI world (rather than in AppDelegate).
class AppDelegate: NSObject, UIApplicationDelegate {
var didFinishLaunching: ((AppDelegate) -> Void)?
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions
launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil
) -> Bool {
didFinishLaunching?(self)
return true
}
}
#main
struct MyApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
#ObservedObject private var applicationModel = ApplicationModel()
// `init` gets called BEFORE `didFinishLaunchingWithOptions`
init() {
// Subscribe to get a `didFinishLaunching` call
appDelegate.didFinishLaunching = { [weak applicationObject] appDelegate in
// Setup any application code...
applicationModel?.setup()
}
}
var body: some Scene {
return WindowGroup {
if applicationObject.isUserLoggedIn {
LoggedInView()
} else {
LoggedOutView()
}
}
}
}

How to use my #EnvironmentObject in AppDelegate in Swiftui (iOS 15 / Xcode 13.2.1)?

I have a class :
class AppInfos_M: ObservableObject {
#Published var currentUser: User_M = User_M()
#Published var userTo: User_M = User_M()
}
where i declare it from main as environmentObject :
...
#main
struct TestApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
...
#StateObject var appInfos_M = AppInfos_M()
var body: some Scene {
WindowGroup {
LaunchScreen_V()
.environmentObject(appInfos_M)
...
}
}
}
the class works very good in my app. Now i need to modify it from AppDelegate because i need to get appInfos_M.userTo.id when i get a notification. I tried several things but no one works. How can i access it?
In all my views where I need it I declare this way and it works fine but not in AppDelegate, why? :
#EnvironmentObject var appInfos_M: AppInfos_M
Here is one of the tests I tried that did not work:
Note that the 3 small dots (...) are for the useless code to put here.
...
class AppDelegate: NSObject, UIApplicationDelegate {...}
...
#available(iOS 10, *)
extension AppDelegate : UNUserNotificationCenterDelegate {
...
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: #escaping () -> Void) {
#EnvironmentObject var appInfos_M: AppInfos_M
let userInfo = response.notification.request.content.userInfo
appInfos_M.userTo.id = "just for testing here" // <- i get this error : Thread 1: Fatal error: No ObservableObject of type AppInfos_M found. A View.environmentObject(_:) for AppInfos_M may be missing as an ancestor of this view.
...
You can always store AppInfos_M in your AppDelegate Like this
class AppDelegate: NSObject, UIApplicationDelegate {
var appInfos = AppInfos_M()
(...)
}
You can then use it as EnvironmentObject as:
...
#main
struct TestApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
...
var body: some Scene {
WindowGroup {
LaunchScreen_V()
.environmentObject(appDelegate.appInfos)
...
}
}
}

Using Firebase Authentication with new #App protocol

I am new to iOS development and am struggling to implement Firebase phone authentication with the new #App protocol in SwiftUI. The documentation for Firebase is written for UIKit, and the tutorials online for SwiftUI use AppDelegate and SceneDelegate instead of the new #main protocol in the App struct.
My concrete questions are as follows: How/where would I inject this authentication state class I have created?
import Foundation
class AuthenticationState: NSObject, ObservableObject
{
#Published var loggedInUser: User?
#Published var isAuthenticating = false
#Published var error: NSError?
static let shared = AuthenticationState();
private let auth = Auth.auth();
fileprivate var currentNonce: String?
func login(with loginOption: LoginOption) {
self.isAuthenticating = true
self.error = nil
switch loginOption {
case .signInWithApple:
handleSignInWithApple()
}
}
func signup(email: String, password: String, passwordConfirmation: String) {
// TODO
}
private func handleSignInWithApple() {
// TODO
}
}
Secondly, the AuthenticationState class does not know about Firebase Auth object, I assume because it is incorrectly configured. So far, I am configuring Firebase in an AppDelegate class:
import UIKit
import Firebase
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions:
[UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
return true
}
}
Whereas, I also have a MapworkApp.swift file, which I believe is supposed to replace AppDelegate, but I am unsure:
import SwiftUI
import Firebase
#main
struct MapworkApp: App {
let persistenceController = PersistenceController.shared
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
}
struct MapworkApp_Previews: PreviewProvider {
static var previews: some View {
/*#START_MENU_TOKEN#*/Text("Hello, World!")/*#END_MENU_TOKEN#*/
}
}
The runtime errors I currently receive:
020-12-16 13:22:34.416917-0700 Mapwork[1351:332863] 7.3.0 - [Firebase/Core][I-COR000003] The default Firebase app has not yet been configured. Add `[FIRApp configure];` (`FirebaseApp.configure()` in Swift) to your application initialization. Read more:
2020-12-16 13:22:34.417240-0700 Mapwork[1351:332633] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'The default FIRApp instance must be configured before the default FIRAuthinstance can be initialized. One way to ensure that is to call `[FIRApp configure];` (`FirebaseApp.configure()` in Swift) in the App Delegate's `application:didFinishLaunchingWithOptions:` (`application(_:didFinishLaunchingWithOptions:)` in Swift).'
*** First throw call stack:
(0x1863a586c 0x19b314c50 0x18629e4a4 0x102bc8918 0x1028465d8 0x1028466f4 0x102845c1c 0x102845be4 0x102ec96c0 0x102ecb1f8 0x18a32c5bc 0x102845c64 0x102854aa0 0x102854cbc 0x18cdba724 0x102854c18 0x102855028 0x185fda6b0)
libc++abi.dylib: terminating with uncaught exception of type NSException
terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'The default FIRApp instance must be configured before the default FIRAuthinstance can be initialized. One way to ensure that is to call `[FIRApp configure];` (`FirebaseApp.configure()` in Swift) in the App Delegate's `application:didFinishLaunchingWithOptions:` (`application(_:didFinishLaunchingWithOptions:)` in Swift).'
Message from debugger: The LLDB RPC server has exited unexpectedly. Please file a bug if you have reproducible steps.
Any help or direction would be greatly appreciated as the docs online are now inapplicable.
For many Firebase APIs, you don't need to use the AppDelegate approach. Instead, you can get away with initialising Firebase in your app's initialiser, like so:
import SwiftUI
import Firebase
#main
struct MapworkApp: App {
init() {
FirebaseApp.configure()
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
This works well for apps that use Cloud Firestore and Firebase Authentication (Sign in with Apple).
For some of Firebase's SDKs you need to implement an AppDelegate. To do so, implement your AppDelegate like this:
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
print("Application is starting up. ApplicationDelegate didFinishLaunchingWithOptions.")
return true
}
// ... add any callbacks your app might require
}
Then, connect your App to the AppDelegate like this:
#main
struct MapworkApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
init() {
FirebaseApp.configure()
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
You might have to move the call to FirebaseApp.configure() to the AppDelegate. In this case, your AppDelegate and App might look like this (you can keep them in the same file, if you like):
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
return true
}
}
#main
struct MapworkApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
To learn more about this, check out my articles about the new application life cycle:
Firebase and the new SwiftUI 2 Application Life Cycle - SwiftUI 2
The Ultimate Guide to the SwiftUI 2 Application Life Cycle - SwiftUI 2
This sample app I wrote shows how to build a simple to-do list app with SwiftUI and Firebase: peterfriese/MakeItSo. The latest version of the code follows the new SwiftUI 2 Application Life Cycle, and includes Sign in with Apple.

Transform UIApplicationDelegate methods into RxSwift Observables

In RxSwift / RxCocoa you can create a reactive wrapper for a delegate (e.g. UIScrollViewDelegate or CLLocationManagerDelegate) to enable Rx observable sequences for certain delegate methods.
I am trying to implement this for the UIApplicationDelegate method applicationDidBecomeActive:
What I tried so far is pretty straightforward and similar to the DelegateProxy subclasses that are included in RxCocoa.
I created my DelegateProxy subclass:
class RxUIApplicationDelegateProxy: DelegateProxy, UIApplicationDelegate, DelegateProxyType {
static func currentDelegateFor(object: AnyObject) -> AnyObject? {
let application: UIApplication = object as! UIApplication
return application.delegate
}
static func setCurrentDelegate(delegate: AnyObject?, toObject object: AnyObject) {
let application: UIApplication = object as! UIApplication
application.delegate = delegate as? UIApplicationDelegate
}
}
And an Rx extension for UIApplication:
extension UIApplication {
public var rx_delegate: DelegateProxy {
return proxyForObject(RxUIApplicationDelegateProxy.self, self)
}
public var rx_applicationDidBecomeActive: Observable<Void> {
return rx_delegate.observe("applicationDidBecomeActive:")
.map { _ in
return
}
}
}
In my AppDelegate I subscribe to the observable:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// the usual setup
// and then:
application.rx_applicationDidBecomeActive
.subscribeNext { _ in
print("Active!")
}
.addDisposableTo(disposeBag)
return true
}
When I start my app "Active!" gets printed and then I get the following crash in RxCocoa's _RXDelegateProxy_ class:
Does anybody have an idea what the problem might be? Or has anybody successfully implemented something like rx_applicationDidBecomeActive?
It looks like a really tricky issue with RxSwift and memory management.
The default implementation of DelegateProxyType sets an instance of a delegate proxy (in this case, RxUIApplicationDelegateProxy) to the delegate of UIApplication.
It also stores the original AppDelegate as a property called forwardToDelegate so all the delegate methods can still be passed to it.
The problem is that, when the new app delegate is set:
application.delegate = delegate as? UIApplicationDelegate
the original one is deallocated! You can check it by overriding deinit in AppDelegate. The reasons are explained in this answer. And because the property forwardToDelegate is of type assign, your app crashes as the property points to a deallocated object.
I have found a workaround for that. I'm not really sure if it is a recommended way, so be warned. You can override a method from DelegateProxyType in RxUIApplicationDelegateProxy:
override func setForwardToDelegate(delegate: AnyObject?, retainDelegate: Bool) {
super.setForwardToDelegate(delegate, retainDelegate: true)
}
In normal circumstances, you don't want to retain the delegate as it leads to a retain cycle. But in this special case, this is not a problem: your UIApplication object will exist the entire time while your application is alive anyway.

Resources