Skip to content

Database.database() is nil despite being nonnull #2419

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
pepasflo opened this issue Feb 21, 2019 · 39 comments
Closed

Database.database() is nil despite being nonnull #2419

pepasflo opened this issue Feb 21, 2019 · 39 comments

Comments

@pepasflo
Copy link

screen shot 2019-02-21 at 5 27 02 pm

^ How could this possibly be happening? you might ask

screen shot 2019-02-21 at 5 27 25 pm

Oh, that's why.

Guys, this is extremely uncool. I've just wasted several hours tearing my hair out over why the completion block in observeSingleEvent is never getting executed, only to discover that my DB ref (which "can't be nil") was nil the entire time.

I'd invite you to take a moment and try to appreciate how unbelievably frustrating this is. With this single line of code, you have ensured a terrible on-boarding experience for new developers: it's not that they hit an error, or hit a crash, its that nothing happens at all, and they have no idea why, and you are actively derailing their intuition about where the problem might be.

Please, for the love of $DEITY, don't use NS_ASSUME_NONNULL_BEGIN.

@google-oss-bot

This comment has been minimized.

@pepasflo pepasflo changed the title NS_ASSUME_NONNULL_BEGIN Friends don't let friends use NS_ASSUME_NONNULL_BEGIN Feb 21, 2019
@ryanwilson
Copy link
Member

ryanwilson commented Feb 22, 2019

Hey @pepasflo, sorry for the troubles you're seeing.

A few questions:

  • Do you have a sample project or code snippet you can share to reproduce this?
  • What version of Firebase are you using?
  • Are you calling FirebaseApp.configure()
  • Is anything being logged to the console when you call Database.database()?

The only situation Database.database() should fail is if FirebaseApp.configure() wasn't called before, and it should never be nil. If you're not getting any exception and database() is still nil, this is 100% a bug and should be fixed right away.

If you could please help provide that information and ideally a sample project we can get it fixed right away, and with the versioning information I can hopefully point you to a version in the meantime that's working as expected.

Sorry again for the troubles and thanks for reporting this, we really appreciate it!

Update: I changed the title to be more specific to the exact issue, hope that's okay.

@ryanwilson ryanwilson pinned this issue Feb 22, 2019
@ryanwilson ryanwilson unpinned this issue Feb 22, 2019
@ryanwilson ryanwilson modified the milestone: M44 Feb 22, 2019
@ryanwilson ryanwilson changed the title Friends don't let friends use NS_ASSUME_NONNULL_BEGIN Database.database() is nil despite being nonnull Feb 22, 2019
@morganchen12
Copy link
Contributor

morganchen12 commented Feb 22, 2019

@ryanwilson we used to have an assertion there that would fail on access if the database instance wasn't nil. Did we remove that?

Edit: Looks like all the assertions are still there.

@ryanwilson
Copy link
Member

@morganchen12 I'm fairly certain we did not - we do check two things though, if the default app is configured here:

if (![FIRApp isDefaultAppConfigured]) {

and in this method (called through the database() call) we check both the app and the URL and throw an exception if either are nil:

+ (FIRDatabase *)databaseForApp:(FIRApp *)app URL:(NSString *)url {

@morganchen12
Copy link
Contributor

I've seen this kind of thing happen before if there's duplicate class implementations in the runtime. @pepasflo can you share a sample that reproduces this issue?

@pepasflo
Copy link
Author

Hi @ryanwilson

Thank you for being gracious and tolerant in the face of my crankiness.

A bit of background -- we've had Firebase working in our iOS app, and I needed to spin up a little demo app to try something out.

I created a new Xcode project, installed Firebase DB via cocoapods, dragged in a copy of our plist file from google, renamed the bundle ID of the demo app to match our production app, and pasted this into AppDelegate:

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        let path = Bundle.main.path(forResource: "firebase-staging-livecoverage", ofType: "plist")!
        let options = FirebaseOptions(contentsOfFile: path)!
        FirebaseApp.configure(options: options)
        let db = Database.database()
        if db == nil { fatalError("❌ db is nil") }
        let ref = db.reference(withPath: "live/9747")
        if ref == nil { fatalError("❌ ref is nil") }
        ref.observeSingleEvent(of: DataEventType.value, with: { (snapshot) in
            print("✅ firebase snapshot: \(snapshot)")
        }, withCancel: { (error) in
            print("❌ firebase error: \(error)")
        })
        window?.rootViewController = UIViewController()
        return true
    }

which fails with Fatal error: ❌ db is nil.

Pasting the same code into our production app works as expected, returning the JSON corresponding to live/9747.

My hunch is that this is 99% likely to be some sort of configuration step which I've missed or other mistake I've made while setting up the demo project.

But, rather than solving my particular problem, hopefully we can figure out the circumstances under which the db is nil and address that issue for everyone.

Happy to follow-up with more details!

@ryanwilson
Copy link
Member

No problem at all, this is an ugly bug and hopefully we can track down what's causing this.

Are you able to paste the contents Podfile.lock (just relevant Firebase lines are fine) so we can verify versions?

In the meantime I'll take a look to see if I can reproduce this.

@pepasflo
Copy link
Author

Hmm, looks like I ended up with different versions of Firebase between the production app and the demo.

Working production app:

$ cat Podfile.lock | grep -i firebase
  - Firebase (5.15.0):
    - Firebase/Core (= 5.15.0)
  - Firebase/Analytics (5.15.0):
    - Firebase/Core
  - Firebase/Core (5.15.0):
    - Firebase/CoreOnly
    - FirebaseAnalytics (= 5.4.0)
  - Firebase/CoreOnly (5.15.0):
    - FirebaseCore (= 5.1.10)
  - Firebase/Database (5.15.0):
    - Firebase/CoreOnly
    - FirebaseDatabase (= 5.0.4)
  - Firebase/RemoteConfig (5.15.0):
    - Firebase/Core
    - FirebaseRemoteConfig (= 3.1.0)
  - FirebaseABTesting (2.0.0):
    - FirebaseCore (~> 5.0)
  - FirebaseAnalytics (5.4.0):
    - FirebaseCore (~> 5.1)
    - FirebaseInstanceID (~> 3.3)
  - FirebaseAuthInterop (1.0.0)
  - FirebaseCore (5.1.10):
  - FirebaseDatabase (5.0.4):
    - FirebaseAuthInterop (~> 1.0)
    - FirebaseCore (~> 5.1)
  - FirebaseInstanceID (3.3.0):
    - FirebaseCore (~> 5.1)
  - FirebaseRemoteConfig (3.1.0):
    - FirebaseABTesting (~> 2.0)
    - FirebaseAnalytics (~> 5.3)
    - FirebaseCore (~> 5.1)
    - FirebaseInstanceID (~> 3.3)
  - Firebase
  - Firebase/Analytics
  - Firebase/Database
  - Firebase/RemoteConfig
  Firebase: 8bb9268bff82374f2cbaaabb143e725743c316ae
  FirebaseABTesting: 1f50b8d50f5e3469eea54e7463a7b7fe221d1f5e
  FirebaseAnalytics: c06f9d70577d79074214700a71fd5d39de5550fb
  FirebaseAuthInterop: 0ffa57668be100582bb7643d4fcb7615496c41fc
  FirebaseCore: 35747502d9e8c6ee217385ad04446c7c2aaf9c5c
  FirebaseDatabase: 0621689f77528d62b47e1c06ca737c4c19275d1a
  FirebaseInstanceID: e2fa4cb35ef5558c200f7f0ad8a53e212215f93e
  FirebaseRemoteConfig: 7e11c65f0769c09bff6947997c209515058c5318

Demo with nil db:

$ cat Podfile.lock | grep -i firebase
  - FirebaseAuthInterop (1.0.0)
  - FirebaseCore (5.2.0):
  - FirebaseDatabase (5.1.0):
    - FirebaseAuthInterop (~> 1.0)
    - FirebaseCore (~> 5.2)
  - FirebaseDatabase
    - FirebaseAuthInterop
    - FirebaseCore
    - FirebaseDatabase
  FirebaseAuthInterop: 0ffa57668be100582bb7643d4fcb7615496c41fc
  FirebaseCore: ea2d1816723ef21492b8e9113303e1350db5e08c
  FirebaseDatabase: 23acb0c53cd4d4070a427b60100b2e4aaa97c45d

@pepasflo
Copy link
Author

Hmm, I'll try recreating that demo and see if I can get it to use the latest Firebase from cocoapods.

@ryanwilson
Copy link
Member

This is super helpful, please do try the latest version and let us know if you get the same issue. I'll be away from the keyboard for a bit but back within the next hour to check in.

@pepasflo
Copy link
Author

Oh, I think I see the versioning issue: I called for FirebaseDatabase in the demo, not Firebase/Database as in our production app.

$ cat Podfile
platform :ios, '12.1'
target 'FirebaseiOSDemo4' do
  use_frameworks!
  pod 'FirebaseDatabase'
end

which results in

$ pod install
Analyzing dependencies
Downloading dependencies
Installing FirebaseAuthInterop (1.0.0)
Installing FirebaseCore (5.3.0)
Installing FirebaseDatabase (5.1.0)
Installing GoogleUtilities (5.3.7)
Installing leveldb-library (1.20)
Generating Pods project
Integrating client project

@pepasflo
Copy link
Author

Aha.

$ cat Podfile
platform :ios, '12.1'
target 'FirebaseiOSDemo5' do
  use_frameworks!
  pod 'Firebase/Database'
end
$ pod install
Analyzing dependencies
Downloading dependencies
Installing Firebase (5.17.0)
Installing FirebaseAuthInterop (1.0.0)
Installing FirebaseCore (5.3.0)
Installing FirebaseDatabase (5.1.0)
Installing GoogleUtilities (5.3.7)
Installing leveldb-library (1.20)
Generating Pods project
Integrating client project

@pepasflo
Copy link
Author

Hmm, same failure in the demo with the latest firebase: Fatal error: ❌ db is nil.

I'll see if I can create a new google plist from scratch for the demo.

@pepasflo
Copy link
Author

Nope. Registered a new app with Firebase, new plist, changed the app ID to match (tv.flosports.ott.staging-livecoverage-demo2), db is still nil.

There must be some setup step I'm overlooking.

@pepasflo
Copy link
Author

Instead of renaming the plist, I went with the default name (GoogleService-Info.plist) and changed the configure line to FirebaseApp.configure(), same error (db is nil)

@pepasflo
Copy link
Author

pepasflo commented Feb 22, 2019

Ok, it turns out this was due to not including Firebase/Core in the Podfile.

This:

$ cat Podfile
platform :ios, '12.1'
target 'FirebaseiOSDemo5' do
  use_frameworks!
  pod 'Firebase/Database'
end

results in the db is nil failure.

This does not:

$ cat Podfile
platform :ios, '12.1'
target 'FirebaseiOSDemo5' do
  use_frameworks!
  pod 'Firebase/Core'
  pod 'Firebase/Database'
end

Update: to be clear, this was the solution to the problem (adding pod 'Firebase/Core' to the Podile)

@ryanwilson
Copy link
Member

Very interesting, thanks so much for tracking this down.

FirebaseCore should be getting pulled in (and I see it there as well), I'm really unsure as to why this is failing. I'll try to find out why Core isn't configuring properly when Firebase/Core isn't explicitly included.

@paulb777
Copy link
Member

Yep, Firebase/Core shouldn't be needed for Database to initialize properly.

@pepasflo
Copy link
Author

Hmm, it looks like Core was already listed as a pod dependency on Database, not sure why this is happening.

    {
      "dependencies": {
        "Firebase/CoreOnly": [

        ],
        "FirebaseDatabase": "5.1.0"
      },
      "name": "Database"
    },

https://github.com/CocoaPods/Specs/blob/master/Specs/0/3/5/Firebase/5.17.0/Firebase.podspec.json#L222-L230

@ryanwilson
Copy link
Member

I created an example project, pasted the exact application(_:didFinishLaunchingWithOptions:) code above, used my own GoogleService-Info.plist and renamed it to firebase-staging-livecoverage.plist, and it did not fail.

I get a Permission Denied error on the database reference (since there's nothing there and my project has default Auth rules set up.

I tried using FirebaseDatabase in the Podfile as well, but it still succeeds.

A few other thoughts:

  • What version of Xcode are you running?
  • Which simulator?
  • Does this happen when you run as a regular app, or is it running as the host of a unit test / UI test?

Thanks again for trying to help us solve this, I want to make sure we can get you back up and running as well as verify all our documentation is correct.

@pepasflo
Copy link
Author

Ok, full reproduction details:

Recreate the failing case

  • Create a new Single-page iOS project in Xcode, named 'JustFirebaseDatabase'
  • Edit the app ID to match that used by your GoogleService-Info.plist
  • Copy your GoogleService-Info.plist into the project
  • Create this Podfile:
platform :ios, '12.1'
target 'JustFirebaseDatabase' do
  use_frameworks!
  pod 'Firebase/Database'
end
  • run pod install
$ pod install
Analyzing dependencies
Downloading dependencies
Installing Firebase (5.17.0)
Installing FirebaseAuthInterop (1.0.0)
Installing FirebaseCore (5.3.0)
Installing FirebaseDatabase (5.1.0)
Installing GoogleUtilities (5.3.7)
Installing leveldb-library (1.20)
Generating Pods project
Integrating client project

this will result in the following Podfile.lock:

$ cat JustFirebaseDatabase/Podfile.lock 
PODS:
  - Firebase/CoreOnly (5.17.0):
    - FirebaseCore (= 5.3.0)
  - Firebase/Database (5.17.0):
    - Firebase/CoreOnly
    - FirebaseDatabase (= 5.1.0)
  - FirebaseAuthInterop (1.0.0)
  - FirebaseCore (5.3.0):
    - GoogleUtilities/Logger (~> 5.2)
  - FirebaseDatabase (5.1.0):
    - FirebaseAuthInterop (~> 1.0)
    - FirebaseCore (~> 5.2)
    - leveldb-library (~> 1.18)
  - GoogleUtilities/Environment (5.3.7)
  - GoogleUtilities/Logger (5.3.7):
    - GoogleUtilities/Environment
  - leveldb-library (1.20)

DEPENDENCIES:
  - Firebase/Database

SPEC REPOS:
  https://github.com/cocoapods/specs.git:
    - Firebase
    - FirebaseAuthInterop
    - FirebaseCore
    - FirebaseDatabase
    - GoogleUtilities
    - leveldb-library

SPEC CHECKSUMS:
  Firebase: 59d557e064217fab6a03ff00baa73c06e73832e6
  FirebaseAuthInterop: 0ffa57668be100582bb7643d4fcb7615496c41fc
  FirebaseCore: c0c4befb82374d6aef64d800e569f47625352edc
  FirebaseDatabase: 23acb0c53cd4d4070a427b60100b2e4aaa97c45d
  GoogleUtilities: 111a012f4c3a29c9e7c954c082fafd6ee3c999c0
  leveldb-library: 08cba283675b7ed2d99629a4bc5fd052cd2bb6a5

PODFILE CHECKSUM: 2cef90d2435fe3ba5a9627ce213f5c36413140e7

COCOAPODS: 1.5.3

Replace AppDelegate.swift with this:

import UIKit
import FirebaseCore
import FirebaseDatabase

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?


    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        FirebaseApp.configure()
        let db = Database.database()
        if db == nil { fatalError("❌ db is nil") }
        let ref = db.reference(withPath: "live/9747")
        if ref == nil { fatalError("❌ ref is nil") }
        ref.observeSingleEvent(of: DataEventType.value, with: { (snapshot) in
            print("✅ firebase snapshot: \(snapshot)")
        }, withCancel: { (error) in
            print("❌ firebase error: \(error)")
        })
        window?.rootViewController = UIViewController()
        return true
    }
}

(replace reference(withPath: "live/9747") with a path which exists in your DB)

  • Run the app, see that it hits the fatalError with ❌ db is nil

Recreate the working case

  • Create a new Single-page iOS project in Xcode, named 'AlsoFirebaseCore'
  • Edit the app ID to match that used by your GoogleService-Info.plist
  • Copy your GoogleService-Info.plist into the project
  • Create this Podfile:
platform :ios, '12.1'
target 'AlsoFirebaseCore' do
  use_frameworks!
  pod 'Firebase/Core'
  pod 'Firebase/Database'
end
  • run pod install
$ pod install
Analyzing dependencies
Downloading dependencies
Installing Firebase (5.17.0)
Installing FirebaseAnalytics (5.6.0)
Installing FirebaseAuthInterop (1.0.0)
Installing FirebaseCore (5.3.0)
Installing FirebaseDatabase (5.1.0)
Installing FirebaseInstanceID (3.5.0)
Installing GoogleAppMeasurement (5.6.0)
Installing GoogleUtilities (5.3.7)
Installing leveldb-library (1.20)
Installing nanopb (0.3.901)
Generating Pods project
Integrating client project

(Notice that it installed a different set of dependencies)

This will result in the following Podfile.lock:

$ cat AlsoFirebaseCore/Podfile.lock 
PODS:
  - Firebase/Core (5.17.0):
    - Firebase/CoreOnly
    - FirebaseAnalytics (= 5.6.0)
  - Firebase/CoreOnly (5.17.0):
    - FirebaseCore (= 5.3.0)
  - Firebase/Database (5.17.0):
    - Firebase/CoreOnly
    - FirebaseDatabase (= 5.1.0)
  - FirebaseAnalytics (5.6.0):
    - FirebaseCore (~> 5.3)
    - FirebaseInstanceID (~> 3.5)
    - GoogleAppMeasurement (= 5.6.0)
    - GoogleUtilities/AppDelegateSwizzler (~> 5.2)
    - GoogleUtilities/MethodSwizzler (~> 5.2)
    - GoogleUtilities/Network (~> 5.2)
    - "GoogleUtilities/NSData+zlib (~> 5.2)"
    - nanopb (~> 0.3)
  - FirebaseAuthInterop (1.0.0)
  - FirebaseCore (5.3.0):
    - GoogleUtilities/Logger (~> 5.2)
  - FirebaseDatabase (5.1.0):
    - FirebaseAuthInterop (~> 1.0)
    - FirebaseCore (~> 5.2)
    - leveldb-library (~> 1.18)
  - FirebaseInstanceID (3.5.0):
    - FirebaseCore (~> 5.3)
    - GoogleUtilities/Environment (~> 5.3)
    - GoogleUtilities/UserDefaults (~> 5.3)
  - GoogleAppMeasurement (5.6.0):
    - GoogleUtilities/AppDelegateSwizzler (~> 5.2)
    - GoogleUtilities/MethodSwizzler (~> 5.2)
    - GoogleUtilities/Network (~> 5.2)
    - "GoogleUtilities/NSData+zlib (~> 5.2)"
    - nanopb (~> 0.3)
  - GoogleUtilities/AppDelegateSwizzler (5.3.7):
    - GoogleUtilities/Environment
    - GoogleUtilities/Logger
    - GoogleUtilities/Network
  - GoogleUtilities/Environment (5.3.7)
  - GoogleUtilities/Logger (5.3.7):
    - GoogleUtilities/Environment
  - GoogleUtilities/MethodSwizzler (5.3.7):
    - GoogleUtilities/Logger
  - GoogleUtilities/Network (5.3.7):
    - GoogleUtilities/Logger
    - "GoogleUtilities/NSData+zlib"
    - GoogleUtilities/Reachability
  - "GoogleUtilities/NSData+zlib (5.3.7)"
  - GoogleUtilities/Reachability (5.3.7):
    - GoogleUtilities/Logger
  - GoogleUtilities/UserDefaults (5.3.7):
    - GoogleUtilities/Logger
  - leveldb-library (1.20)
  - nanopb (0.3.901):
    - nanopb/decode (= 0.3.901)
    - nanopb/encode (= 0.3.901)
  - nanopb/decode (0.3.901)
  - nanopb/encode (0.3.901)

DEPENDENCIES:
  - Firebase/Core
  - Firebase/Database

SPEC REPOS:
  https://github.com/cocoapods/specs.git:
    - Firebase
    - FirebaseAnalytics
    - FirebaseAuthInterop
    - FirebaseCore
    - FirebaseDatabase
    - FirebaseInstanceID
    - GoogleAppMeasurement
    - GoogleUtilities
    - leveldb-library
    - nanopb

SPEC CHECKSUMS:
  Firebase: 59d557e064217fab6a03ff00baa73c06e73832e6
  FirebaseAnalytics: 75e4bbc6417d190cc98ec1f17c41a4fad4c2c976
  FirebaseAuthInterop: 0ffa57668be100582bb7643d4fcb7615496c41fc
  FirebaseCore: c0c4befb82374d6aef64d800e569f47625352edc
  FirebaseDatabase: 23acb0c53cd4d4070a427b60100b2e4aaa97c45d
  FirebaseInstanceID: 4522aad88f69297622062c0e9ffccdee3dd9b151
  GoogleAppMeasurement: 008e04ecd8efedd97a693aea8634aefe220bd26e
  GoogleUtilities: 111a012f4c3a29c9e7c954c082fafd6ee3c999c0
  leveldb-library: 08cba283675b7ed2d99629a4bc5fd052cd2bb6a5
  nanopb: 2901f78ea1b7b4015c860c2fdd1ea2fee1a18d48

PODFILE CHECKSUM: 573b44ea9327c330f8243f128b572a217d0dc676

COCOAPODS: 1.5.3

Replace AppDelegate.swift with this:

import UIKit
import FirebaseCore
import FirebaseDatabase

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?


    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        FirebaseApp.configure()
        let db = Database.database()
        if db == nil { fatalError("❌ db is nil") }
        let ref = db.reference(withPath: "live/9747")
        if ref == nil { fatalError("❌ ref is nil") }
        ref.observeSingleEvent(of: DataEventType.value, with: { (snapshot) in
            print("✅ firebase snapshot: \(snapshot)")
        }, withCancel: { (error) in
            print("❌ firebase error: \(error)")
        })
        window?.rootViewController = UIViewController()
        return true
    }
}

(replace reference(withPath: "live/9747") with a path which exists in your DB)

  • Run the app, see that it succeeds with ✅ firebase snapshot: Snap (9747) {...

Podfile.lock differences

The only difference between these two projects seems to be the dependencies which get installed:

$ diff -urN JustFirebaseDatabase/Podfile.lock AlsoFirebaseCore/Podfile.lock 
--- JustFirebaseDatabase/Podfile.lock	2019-02-22 12:43:18.000000000 -0600
+++ AlsoFirebaseCore/Podfile.lock	2019-02-22 12:49:58.000000000 -0600
@@ -1,9 +1,21 @@
 PODS:
+  - Firebase/Core (5.17.0):
+    - Firebase/CoreOnly
+    - FirebaseAnalytics (= 5.6.0)
   - Firebase/CoreOnly (5.17.0):
     - FirebaseCore (= 5.3.0)
   - Firebase/Database (5.17.0):
     - Firebase/CoreOnly
     - FirebaseDatabase (= 5.1.0)
+  - FirebaseAnalytics (5.6.0):
+    - FirebaseCore (~> 5.3)
+    - FirebaseInstanceID (~> 3.5)
+    - GoogleAppMeasurement (= 5.6.0)
+    - GoogleUtilities/AppDelegateSwizzler (~> 5.2)
+    - GoogleUtilities/MethodSwizzler (~> 5.2)
+    - GoogleUtilities/Network (~> 5.2)
+    - "GoogleUtilities/NSData+zlib (~> 5.2)"
+    - nanopb (~> 0.3)
   - FirebaseAuthInterop (1.0.0)
   - FirebaseCore (5.3.0):
     - GoogleUtilities/Logger (~> 5.2)
@@ -11,31 +23,70 @@
     - FirebaseAuthInterop (~> 1.0)
     - FirebaseCore (~> 5.2)
     - leveldb-library (~> 1.18)
+  - FirebaseInstanceID (3.5.0):
+    - FirebaseCore (~> 5.3)
+    - GoogleUtilities/Environment (~> 5.3)
+    - GoogleUtilities/UserDefaults (~> 5.3)
+  - GoogleAppMeasurement (5.6.0):
+    - GoogleUtilities/AppDelegateSwizzler (~> 5.2)
+    - GoogleUtilities/MethodSwizzler (~> 5.2)
+    - GoogleUtilities/Network (~> 5.2)
+    - "GoogleUtilities/NSData+zlib (~> 5.2)"
+    - nanopb (~> 0.3)
+  - GoogleUtilities/AppDelegateSwizzler (5.3.7):
+    - GoogleUtilities/Environment
+    - GoogleUtilities/Logger
+    - GoogleUtilities/Network
   - GoogleUtilities/Environment (5.3.7)
   - GoogleUtilities/Logger (5.3.7):
     - GoogleUtilities/Environment
+  - GoogleUtilities/MethodSwizzler (5.3.7):
+    - GoogleUtilities/Logger
+  - GoogleUtilities/Network (5.3.7):
+    - GoogleUtilities/Logger
+    - "GoogleUtilities/NSData+zlib"
+    - GoogleUtilities/Reachability
+  - "GoogleUtilities/NSData+zlib (5.3.7)"
+  - GoogleUtilities/Reachability (5.3.7):
+    - GoogleUtilities/Logger
+  - GoogleUtilities/UserDefaults (5.3.7):
+    - GoogleUtilities/Logger
   - leveldb-library (1.20)
+  - nanopb (0.3.901):
+    - nanopb/decode (= 0.3.901)
+    - nanopb/encode (= 0.3.901)
+  - nanopb/decode (0.3.901)
+  - nanopb/encode (0.3.901)
 
 DEPENDENCIES:
+  - Firebase/Core
   - Firebase/Database
 
 SPEC REPOS:
   https://github.com/cocoapods/specs.git:
     - Firebase
+    - FirebaseAnalytics
     - FirebaseAuthInterop
     - FirebaseCore
     - FirebaseDatabase
+    - FirebaseInstanceID
+    - GoogleAppMeasurement
     - GoogleUtilities
     - leveldb-library
+    - nanopb
 
 SPEC CHECKSUMS:
   Firebase: 59d557e064217fab6a03ff00baa73c06e73832e6
+  FirebaseAnalytics: 75e4bbc6417d190cc98ec1f17c41a4fad4c2c976
   FirebaseAuthInterop: 0ffa57668be100582bb7643d4fcb7615496c41fc
   FirebaseCore: c0c4befb82374d6aef64d800e569f47625352edc
   FirebaseDatabase: 23acb0c53cd4d4070a427b60100b2e4aaa97c45d
+  FirebaseInstanceID: 4522aad88f69297622062c0e9ffccdee3dd9b151
+  GoogleAppMeasurement: 008e04ecd8efedd97a693aea8634aefe220bd26e
   GoogleUtilities: 111a012f4c3a29c9e7c954c082fafd6ee3c999c0
   leveldb-library: 08cba283675b7ed2d99629a4bc5fd052cd2bb6a5
+  nanopb: 2901f78ea1b7b4015c860c2fdd1ea2fee1a18d48
 
-PODFILE CHECKSUM: 2cef90d2435fe3ba5a9627ce213f5c36413140e7
+PODFILE CHECKSUM: 573b44ea9327c330f8243f128b572a217d0dc676
 
 COCOAPODS: 1.5.3

My guess

Since most of this is written in Obj-C, my first guess is that if a certain dependency isn't installed, this ends up translating into an object being nil somewhere inside of Firebase, and that object is being called without being checked (which results in nothing happening at all, because Obj-c is nil-tolerant).

Of course, I haven't delved into the code, so take that with a grain of salt :)

@pepasflo
Copy link
Author

pepasflo commented Feb 22, 2019

To summarize, my understanding is that this (missing) single line in the Podfile is what causes this issue:

$ diff -urN JustFirebaseDatabase/Podfile AlsoFirebaseCore/Podfile
--- JustFirebaseDatabase/Podfile	2019-02-22 12:42:51.000000000 -0600
+++ AlsoFirebaseCore/Podfile	2019-02-22 12:49:51.000000000 -0600
@@ -1,5 +1,6 @@
 platform :ios, '12.1'
-target 'JustFirebaseDatabase' do
+target 'AlsoFirebaseCore' do
   use_frameworks!
+  pod 'Firebase/Core'
   pod 'Firebase/Database'
 end

(what led to discovering this was going back to the "getting started" docs, noticing that they included pod Firebase/Core rather than pod Firebase/Database, and despite my intuition telling me that shouldn't make any difference (because Firebase/Core is already a dependency of Firebase/Database, I added that line anyway and that ended up being the solution)

@pepasflo
Copy link
Author

Here's the difference in resulting installed dependencies in a bit easier to read form:

$ cat JustFirebaseDatabase/Podfile.lock | grep '-' | grep -v 'leveldb-' | awk '{print $2}' | sort | uniq > /tmp/justdb.txt
$ cat AlsoFirebaseCore/Podfile.lock | grep '-' | grep -v 'leveldb-' | awk '{print $2}' | sort | uniq > /tmp/alsocore.txt
$ diff -urN /tmp/justdb.txt /tmp/alsocore.txt 
--- /tmp/justdb.txt	2019-02-22 13:11:43.000000000 -0600
+++ /tmp/alsocore.txt	2019-02-22 13:11:56.000000000 -0600
@@ -1,9 +1,23 @@
+"GoogleUtilities/NSData+zlib
+"GoogleUtilities/NSData+zlib"
 Firebase
+Firebase/Core
 Firebase/CoreOnly
 Firebase/Database
+FirebaseAnalytics
 FirebaseAuthInterop
 FirebaseCore
 FirebaseDatabase
+FirebaseInstanceID
+GoogleAppMeasurement
 GoogleUtilities
+GoogleUtilities/AppDelegateSwizzler
 GoogleUtilities/Environment
 GoogleUtilities/Logger
+GoogleUtilities/MethodSwizzler
+GoogleUtilities/Network
+GoogleUtilities/Reachability
+GoogleUtilities/UserDefaults
+nanopb
+nanopb/decode
+nanopb/encode

@ryanwilson
Copy link
Member

@pepasflo I appreciate the full set of details!

Attempting to match your setup, with any differences that matter in bold:

  • Created thisPodfile:
platform :ios, '12.1'
target 'DatabaseNilError' do
  use_frameworks!
  pod 'Firebase/Database'
end

Only the project name is different.

  • ran pod install
$ pod install
Analyzing dependencies
Downloading dependencies
Installing Firebase (5.17.0)
Installing FirebaseAuthInterop (1.0.0)
Installing FirebaseCore (5.3.0)
Installing FirebaseDatabase (5.1.0)
Installing GoogleUtilities (5.3.7)
Installing leveldb-library (1.20)
Generating Pods project
Integrating client project

Same as your output.

Podfile.lock contents:

PODS:
  - Firebase/CoreOnly (5.17.0):
    - FirebaseCore (= 5.3.0)
  - Firebase/Database (5.17.0):
    - Firebase/CoreOnly
    - FirebaseDatabase (= 5.1.0)
  - FirebaseAuthInterop (1.0.0)
  - FirebaseCore (5.3.0):
    - GoogleUtilities/Logger (~> 5.2)
  - FirebaseDatabase (5.1.0):
    - FirebaseAuthInterop (~> 1.0)
    - FirebaseCore (~> 5.2)
    - leveldb-library (~> 1.18)
  - GoogleUtilities/Environment (5.3.7)
  - GoogleUtilities/Logger (5.3.7):
    - GoogleUtilities/Environment
  - leveldb-library (1.20)

DEPENDENCIES:
  - Firebase/Database

SPEC REPOS:
  https://github.com/cocoapods/specs.git:
    - Firebase
    - FirebaseAuthInterop
    - FirebaseCore
    - FirebaseDatabase
    - GoogleUtilities
    - leveldb-library

SPEC CHECKSUMS:
  Firebase: 59d557e064217fab6a03ff00baa73c06e73832e6
  FirebaseAuthInterop: 0ffa57668be100582bb7643d4fcb7615496c41fc
  FirebaseCore: c0c4befb82374d6aef64d800e569f47625352edc
  FirebaseDatabase: 23acb0c53cd4d4070a427b60100b2e4aaa97c45d
  GoogleUtilities: 111a012f4c3a29c9e7c954c082fafd6ee3c999c0
  leveldb-library: 08cba283675b7ed2d99629a4bc5fd052cd2bb6a5

PODFILE CHECKSUM: e5fd3d51dbdd4a9923fb821fdc992f4390e4186e

COCOAPODS: 1.6.0

Difference: CocoaPods version.

Replaced the AppDelegate.swift with the exact same code. I didn't bother to place the item in the Database since it should fail before then.

Did not fail with fatalError. Output to the console:

2019-02-22 14:31:26.598912-0500 DatabaseNilError[8205:19353874] 5.17.0 - [Firebase/Core][I-COR000022] Firebase Analytics is not available. To add it, include Firebase/Core in the Podfile or add FirebaseAnalytics.framework to the Link Build Phase
2019-02-22 14:31:26.957198-0500 DatabaseNilError[8205:19353873] 5.17.0 - [Firebase/Database][I-RDB038012] Listener at /live/9747 failed: permission_denied
❌ firebase error: Error Domain=com.firebase Code=1 "Permission Denied" UserInfo={NSLocalizedDescription=Permission Denied}

The biggest difference appears to be CocoaPods 1.5.3 vs 1.6.0 which shouldn't affect this but I still want to try to reproduce it.

Does anything get logged to the console for you in the failure case?

@pepasflo
Copy link
Author

@ryanwilson oh interesting, I've never had anything trigger that error block yet.

This is what I see in the console:

2019-02-22 16:28:02.201806-0600 JustFirebaseDatabase[83479:8944760] libMobileGestalt MobileGestalt.c:890: MGIsDeviceOneOfType is not supported on this platform.
2019-02-22 16:28:02.307509-0600 JustFirebaseDatabase[83479:8944868] 5.17.0 - [Firebase/Core][I-COR000022] Firebase Analytics is not available. To add it, include Firebase/Core in the Podfile or add FirebaseAnalytics.framework to the Link Build Phase
Fatal error: ❌ db is nil: file /Users/jpepas/projects/JustFirebaseDatabase/JustFirebaseDatabase/AppDelegate.swift, line 22
2019-02-22 16:28:05.488101-0600 JustFirebaseDatabase[83479:8944760] Fatal error: ❌ db is nil: file /Users/jpepas/projects/JustFirebaseDatabase/JustFirebaseDatabase/AppDelegate.swift, line 22

Note: what started me down this rabbit hole was trying to get Firebase working on tvOS. I had created a similar trivial demo app, noticed that it did work, and fell back to creating a trivial iOS demo as a sanity-check (which is what let to this thread). I'm happy to report that I have Firebase/Database working on tvOS after following the example provided with firebase-ios-sdk: #10 (comment)

@ryanwilson
Copy link
Member

I wonder if the tvOS bug is related to #2157. Is it possible your project has -ObjC missing? It shouldn't be the case, but worth a look.

Otherwise, I can help you try to step through this to debug it.

As a very brief overview, FirebaseCore has a component system to create, cache, and deallocate the singletons of each SDK. The singleton should be created as follows:

  1. FirebaseDatabase registers as a FIRLibrary in a +load method.
    a) FirebaseDatabase provides a method called componentsToRegister:, this is done in FIRDatabaseComponent.m, registering a protocol with functionality that the instance provides. In this case, it's just returning instances of Database.
  2. FirebaseApp.configure() is called.
    a) This creates a FIRComponentContainer and should call that componentsToRegister: on all registered libraries.
  3. The user tries to access Database.database().
    a) This asks the FIRComponentContainer for an instance with the above registered protocol.
    b) If the instance is nil, it'll try to create it with the creation block from componentsToRegister:.
    c) In Database's situation, it'll return a FIRDatabaseComponent that handles instance management.

Somewhere along those lines it's failing, it would be great if we could find out where but unfortunately I still can't reproduce it.

Here are the code locations for each associated step, if you could try to set breakpoints and follow along to see where it drops off:

  1. [FIRApp registerInternalLibrary:(Class<FIRLibrary>)self
    This must be called, if not there's no chance of Database ever being initialized.
    a) Shouldn't be triggered yet, see step 2a below.

  2. In your own code
    a)

    The database provider should be nonnull here.

  3. With no crashes, it's at least getting to here:

    id<FIRDatabaseProvider> provider = FIR_COMPONENT(FIRDatabaseProvider, app.container);

    a) The instance fetching from the componentContainer should happen here:
    // Check if there is a cached instance, and return it if so.

    b) Since the above should be nil, the instance should be created in This should return a valid instance now of FIRDatabaseComponent.
    c)
    NSURL *databaseUrl = [NSURL URLWithString:url];
    should be called now to actually create the instance of Database and return it to you.

I know this system is a little complex (tl;dr - it's needed because an instance of Database isn't valid without a FirebaseApp) but hopefully that helps summarize it. Happy to answer any questions about it or clarify further, but hopefully we can find where in that series of steps things fail for you.

@pepasflo
Copy link
Author

@ryanwilson thanks for the details steps, I'll set some breakpoints later today

@pepasflo
Copy link
Author

pepasflo commented Feb 26, 2019

Hmm, so under the JustFirebaseDatabase project, the [FIRDatabaseComponent load] method breakpoint never gets hit. Under AlsoFirebaseCore, it does.

screen shot 2019-02-26 at 10 44 05 am

I don't even understand how that's possible 😟

(edit: s/FirebaseDatabase/FIRDatabaseComponent/)

@paulb777
Copy link
Member

Is -ObjC included in the Other Linker Flags Build Setting?

@pepasflo
Copy link
Author

Ahhh, it is not.

$ grep -r -I -i objc JustFirebaseDatabase AlsoFirebaseCore 2>/dev/null | grep '\.xcconfig' | grep -e '-ObjC'
AlsoFirebaseCore/Pods/Target Support Files/Pods-AlsoFirebaseCore/Pods-AlsoFirebaseCore.release.xcconfig:OTHER_LDFLAGS = $(inherited) -ObjC -l"c++" -l"icucore" -l"sqlite3" -l"z" -framework "CFNetwork" -framework "FIRAnalyticsConnector" -framework "FirebaseAnalytics" -framework "FirebaseCore" -framework "FirebaseCoreDiagnostics" -framework "FirebaseDatabase" -framework "FirebaseInstanceID" -framework "Foundation" -framework "GoogleAppMeasurement" -framework "GoogleUtilities" -framework "Security" -framework "StoreKit" -framework "SystemConfiguration" -framework "leveldb" -framework "nanopb"
AlsoFirebaseCore/Pods/Target Support Files/Pods-AlsoFirebaseCore/Pods-AlsoFirebaseCore.debug.xcconfig:OTHER_LDFLAGS = $(inherited) -ObjC -l"c++" -l"icucore" -l"sqlite3" -l"z" -framework "CFNetwork" -framework "FIRAnalyticsConnector" -framework "FirebaseAnalytics" -framework "FirebaseCore" -framework "FirebaseCoreDiagnostics" -framework "FirebaseDatabase" -framework "FirebaseInstanceID" -framework "Foundation" -framework "GoogleAppMeasurement" -framework "GoogleUtilities" -framework "Security" -framework "StoreKit" -framework "SystemConfiguration" -framework "leveldb" -framework "nanopb"

@pepasflo
Copy link
Author

So, one of these podspecs must be setting -ObjC:

+Firebase/Core
+FirebaseAnalytics
+FirebaseInstanceID
+GoogleAppMeasurement
+GoogleUtilities/AppDelegateSwizzler
+GoogleUtilities/MethodSwizzler
+GoogleUtilities/Network
+GoogleUtilities/Reachability
+GoogleUtilities/UserDefaults
+nanopb
+nanopb/decode
+nanopb/encode

@paulb777
Copy link
Member

CocoaPods should be adding -ObjC automatically.

@pepasflo
Copy link
Author

pepasflo commented Feb 26, 2019

So it sounds like the situation is:

  • CocoaPods 1.5.3
    • JustFirebaseDatabase
      • Does not Automatically add the -ObjC flag
    • AlsoFirebaseCore
      • Automatically adds the -ObjC flag
  • CocoaPods 1.6.0
    • JustFirebaseDatabase
      • Automatically adds the -ObjC flag
    • AlsoFirebaseCore
      • Automatically adds the -ObjC flag

@paulb777
Copy link
Member

Yep. I should have remembered CocoaPods/CocoaPods#7946 sooner when you said you were doing tvOS.

@pepasflo
Copy link
Author

Would it be reasonable to add -ObjC to the podspec, to prevent other users who aren't on the latest version of cocoapods from hitting this bug?

@pepasflo
Copy link
Author

Just for some perspective, our team is actually still on cocoapods 1.4.0 (I use a more up-to-date version for personal / one-off projects).

Why would we do this?

It turns out that different versions of cocoapods, despite being essentially feature compatible, will produce very different Xcode project files. If you have two team members using different versions of cocoapods, the result is that a tiny change to the pod file file will cause a giant amount of noise in the PR if a different version of cocoapods is used.

So there's a pressure on teams to all stick to the same version of cocoapods. The easiest way to ensure everyone is on the same page is to add a Gemfile to the project which specifies a particular version of cocoapods. At that point, keeping the team up-to-date with the latest version of cocoapods becomes a maintenance task, which competes with all of the other priorities of the team, and if it ain't broke...

This leads me to suspect there may be a lot of teams who are using an older version of cocoapods.

@pepasflo
Copy link
Author

Just for reference, here is how we avoid having to type bundle exec pod all of the time, to ensure that we use the project's version of pod, and not any other version of pod (e.g. from homebrew):

We have a script called pod in our project. If it finds a Gemfile in any parent directly, it runs bundle exec pod. If not, it uses whatever the system pod is.

We then symlink this pod script into our $PATH.

Here is the pod script:

#!/usr/bin/env python

# A wrapper for 'pod' which detects if it should run 'bundle exec pod' or
# use the system 'pod' command, based on the presence of a 'Gemfile'.

# If a Gemfile exists in any directory above pwd, 'bundle exec pod' should
# be used.  Otherwise, the system 'pod' is used.

# Expected behavior:
#    $ pwd
#    /Users/foo/github/some_repo
#    $ pod --version
#    1.4.0
#    $ cd /tmp
#    $ pod --version
#    1.5.3

import os
import sys

def gemfile_exists():
    # Ascend up the directory tree looking for a Gemfile.
    gempath = 'Gemfile'
    while True:
        if os.path.exists(gempath):
            return True
        else:
            if os.path.abspath(gempath) == '/Gemfile':
                return False
            else:
                gempath = '../' + gempath
                continue

if __name__ == "__main__":
    if gemfile_exists():
        cmd = ['bundle', 'exec', 'pod'] + sys.argv[1:]
    else:
        scriptdir = os.path.dirname(__file__)
        pathchunks = os.environ["PATH"].split(':')
        if scriptdir in pathchunks:
            os.environ["PATH"] = ':'.join(
                filter(lambda x: x != scriptdir, pathchunks)
            )
        else:
            # We can't find an exact match in $PATH, so fall back to the
            # alternate strategy of chopping one leading component from $PATH
            # and recursing.
            # (To instead chop a trailing component, change '[1:]' to '[:1]')
            os.environ["PATH"] = ':'.join(pathchunks[1:])
        cmd = ['pod'] + sys.argv[1:]

    os.execvp(cmd[0], cmd)

@paulb777
Copy link
Member

@pepasflo Thanks for sharing your CocoaPods workflow and all of your input on this bug!

One approach we use to manage CocoaPods and Xcode project files is to simplify them by checking them in after running pod deintegrate. And we're exploring taking advantage of the recently introduced CocoaPods test spec feature to eliminate checking in Xcode project files completely.

I'm hesitant to add workarounds and extra complexity to our implementation to work around a fixed CocoaPods bug, especially for tvOS which is still community supported for us. I'd prefer to improve the documentation to address it and happy to accept such PRs.

@pepasflo
Copy link
Author

Sounds good, thanks everyone for your prompt and friendly responses on this, especially considering my initial bad attitude.

@firebase firebase locked and limited conversation to collaborators Oct 21, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants