0

I'm accessing the Firebase Real-time database from a SpringBoot App with privileged access to the database. That's why we use the JAVA Admin SDK. All is set up and works correctly. We use authentication without issues, and storing data works as well. The problem is in retrieving the data with a ValueEventListener. The onDataChange Event is simply never executed. When testing this I set multiple breakpoints, but these are not hit and neither are the logs or print statements outputted. From what I understand from the docs it should at least be called once with the initial data, which is all I need.

Our Springboot App is written in Kotlin and the relevant code is in the getRelatedData() function. The insert works as expected and I verified the data has been inserted correctly. Thanks for the help already.

@Service
class AccountRelatedDataService(private val database: FirebaseDatabase) {

    fun insertRelatedData(accountRelatedData: AccountRelatedData) {
        val accountRelatedDataRef = database.getReference(refPath)
        accountRelatedDataRef.child(accountRelatedData.fireBaseUID).setValueAsync(accountRelatedData).get()
    }

    fun getRelatedData(accountRelatedData: AccountRelatedData) {
        val accountRelatedDataRef = database.getReference(refPath)

        val listener = object : ValueEventListener {

            @Override
            override fun onDataChange(dataSnapshot: DataSnapshot) {
                val data = dataSnapshot.getValue(AccountRelatedData::class.java)
                log.info("In onDataChange method")
                println(data)
            }

            @Override
            override fun onCancelled(databaseError: DatabaseError) {
                log.info("In onCancelled method")
                println("The read failed: " + databaseError.code)
            }
        }

        accountRelatedDataRef.addValueEventListener(listener)
    }

    companion object {
        private val log = LoggerFactory.getLogger(AccountRelatedDataService::class.java)
        private const val refPath = "accountRelatedData"
    }

Inserting the data works fine on the same database object.

enter image description here

Note the database is wired in via Spring in the constructor. It is a Bean defined as:

@Bean
    fun firebaseDatabase(): FirebaseDatabase {
        return FirebaseDatabase.getInstance()
            ?: throw java.lang.IllegalStateException("Firebase database not available")
    }

In response to the comment from Frank van Puffelen. For the insertRelatedData I get normal logs like:

2021-09-02 20:58:21.619 DEBUG 4270 --- [database-worker] c.g.f.database.connection.Connection     : [conn_0] Sending data: {t=d, d={a=p, r=2, b={p=accountRelatedData/1563c2f2-d95e-47d5-b0a8-6a63db617f13, d={cryptoWallets={ETH={ticker=ETH, address=0x0234b8305a7a821277c0b09951e5a536af0c4d24, validFrom={nano=860000000, epochSecond=1630609099}}}, fireBaseUID=1563c2f2-d95e-47d5-b0a8-6a63db617f13}}}}
2021-09-02 20:58:21.619 DEBUG 4270 --- [database-worker] c.g.f.d.connection.WebsocketConnection   : [ws_0] Reset keepAlive. Remaining: 44998
2021-09-02 20:58:21.739 DEBUG 4270 --- [ebsocket-worker] c.g.f.d.connection.WebsocketConnection   : [ws_0] WS message: {"t":"d","d":{"r":2,"b":{"s":"ok","d":""}}}
2021-09-02 20:58:21.739 DEBUG 4270 --- [database-worker] c.g.f.d.connection.WebsocketConnection   : [ws_0] Reset keepAlive. Remaining: 44879
2021-09-02 20:58:21.739 DEBUG 4270 --- [database-worker] c.g.f.d.connection.WebsocketConnection   : [ws_0] Handle new frame count: 1
2021-09-02 20:58:21.739 DEBUG 4270 --- [database-worker] c.g.f.d.connection.WebsocketConnection   : [ws_0] Parsed complete frame: {t=d, d={r=2, b={s=ok, d=}}}
2021-09-02 20:58:21.739 DEBUG 4270 --- [database-worker] c.g.f.database.connection.Connection     : [conn_0] Received data message: {r=2, b={s=ok, d=}}
2021-09-02 20:58:21.739 DEBUG 4270 --- [database-worker] c.g.f.d.connection.PersistentConnection  : [pc_0] p response: {s=ok, d=}

For the getAccountRelatedData I get no log statements at all from the Firebase client, despite log level being DEBUG.

  • Is your onDataChange or onCancelled even called? – Alex Mamo Sep 02 '21 at 07:15
  • No that's the exact problem. It should be called at least when connecting with the initial data, but Isn’t. – André van der Heijden Sep 02 '21 at 07:38
  • In that case, are you sure you have an internet connection on the user's device? How is your `database` object defined? What is your database location? – Alex Mamo Sep 02 '21 at 07:42
  • That is all setup correctly, as the insertRelatedData works without problem on the same database. Let me put a printscreen of the data in the post. – André van der Heijden Sep 02 '21 at 07:50
  • So there is just a `Bean` in our config that contains the database, then we wire it into the AccountRelatedDataService with Spring. The Bean snippet is in the Original Post now. Then there is a config file with the relevant properties for Firebase etc. – André van der Heijden Sep 02 '21 at 07:52
  • To be clear this runs inside SpringBoot, on my laptop. I've written a standard Spring integration test to replicate the issue. So no user device involved. – André van der Heijden Sep 02 '21 at 07:56
  • 1
    Hoy André. Just to be clear: you're saying that your `accountRelatedDataRef.addValueEventListener(listener)` is execute, but neither the `onDataChange` nor the `onCancelled` is then called, while writes from the same process succeed? If so, is there any debug logging from the database client being written shortly after you attach the listener? – Frank van Puffelen Sep 02 '21 at 14:20
  • Yes indeed you are exactly right. I posted the DEBUG logs for the insert in the post, the get doesn't show any DEBUG info from the database client. – André van der Heijden Sep 02 '21 at 19:03

1 Answers1

0

I think I know what I did wrong. When reading from Firebase it's async, so the listener is added but the code is non-blocking so the function just ends. The database client has no chance to even connect or listen for events.

What I did now is the following, using a CompletableFuture and wait for that to get completed by the onDataChange callback. In this way I can wait for the result and if it takes too long, let it timeout. This result I'll then use to pass back to the controller, and thus to the client that called our REST API.

The adapted code for getRelatedData:

fun getRelatedData() : AccountRelatedData? {
        var completableFuture = CompletableFuture<AccountRelatedData>()

        val listener = object : ValueEventListener {

            @Override
            override fun onDataChange(dataSnapshot: DataSnapshot) {
                val data = dataSnapshot.getValue(AccountRelatedData::class.java)
                log.info("In onDataChange method")
                println(data)
                completableFuture.complete(data)
            }

            @Override
            override fun onCancelled(databaseError: DatabaseError) {
                log.info("In onCancelled method")
                println("The read failed: " + databaseError.code)
            }
        }
        accountRelatedDataRef.addValueEventListener(listener)
        return completableFuture.get(10, TimeUnit.SECONDS)
    }