4

I am trying to implement a Worker from Android WorkManager with an Event listener for Firebase database reference. It works fine if the app is in foreground/background. But once I close the app and the worker runs the event listener is not triggering, As far as I can tell I am not getting any error messages related to this in log.

Here is a sample code:

class FirebaseWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
    val firebaseDatabaseRef = FirebaseDatabase.getInstance().reference
    firebaseDatabaseRef.addListenerForSingleValueEvent(object : ValueEventListener {
        override fun onDataChange(dataSnapshot: DataSnapshot) {
            // Handle DataSnapshot
        }

        override fun onCancelled(databaseError: DatabaseError) {
            // Handle DatabaseError
        }
    })

    return Result.SUCCESS
  }
}

Please let me know if there is anything I can do to get and handle Firebase realtime database data in background when app is closed.

Alex Kulinkovich
  • 4,408
  • 15
  • 46
  • 50
Sasi Kanth
  • 709
  • 8
  • 17
  • Could you please add your manifest declaration? have you used android:process for the service? – Anis BEN NSIR Oct 03 '18 at 08:47
  • @AnisBENNSIR There is no unique declarations of worker in AndroidManifest file. Android WorkManager automatically selects appropriate way to handle the work based on API level. It didn't mention anything related to AndroidManifest declarations in documentation. https://developer.android.com/topic/libraries/architecture/workmanager/basics – Sasi Kanth Oct 03 '18 at 08:51
  • So sorry i have confused between Jobs and worker... For your issue, i think that when the application is on foreground/background the FirebaseDataBase is already initialized, but when application get killed, you have to initialize the data base... The documentation mention a wired behavior if the database init is called on other process... could you please check that the work is executed on your main process? – Anis BEN NSIR Oct 03 '18 at 09:24
  • That might be the case. I have to try it after work, I will check if FirebaseApp is initialised or not. I am not getting any exceptions (It usually throws a exception if there is no FirebaseApp instance present) – Sasi Kanth Oct 03 '18 at 10:28
  • from documentation, https://firebase.google.com/docs/reference/android/com/google/firebase/FirebaseApp?authuser=19&hl=pt . Any FirebaseApp initialization must occur only in the main process of the app. Use of Firebase in processes other than the main process is not supported and will likely cause problems related to resource contention.... – Anis BEN NSIR Oct 03 '18 at 10:33
  • So I have checked the instance of FirebaseApp and FirebaseDatabase and they bother are available. I can get reference path from database and all. But the event listener is not called that's all. – Sasi Kanth Oct 03 '18 at 15:28

2 Answers2

9

You need to block execution of doWork() until all the work is complete. Right now, since addListenerForSingleValueEvent is asynchronous, your function is returning SUCCESS immediately, which means that WorkManager assumes that everything is done and allows your app process to stop.

One way to get your function to block is to use a CountDownLatch to force your code to wait until the listener is complete:

override fun doWork(): Result {
    val latch = CountDownLatch(1)
    val firebaseDatabaseRef = FirebaseDatabase.getInstance().reference
    firebaseDatabaseRef.addListenerForSingleValueEvent(object : ValueEventListener {
        override fun onDataChange(dataSnapshot: DataSnapshot) {
            // Handle DataSnapshot
            latch.countDown()
        }

        override fun onCancelled(databaseError: DatabaseError) {
            // Handle DatabaseError
            latch.countDown()
        }
    })

    latch.await()
    return Result.SUCCESS
}

You may also want to consider when to return a different result code in order to tell WorkManager if your work completed should retried or not.

Sasi Kanth
  • 709
  • 8
  • 17
Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
  • I have tried this approach. But unfortunately this doesn't seem to be working, If I didn't set a timeout for Countdownlatch or Tasks it runs forever. This works fine when app is in foreground/background, But I am having issues when app is closed and want to get data. – Sasi Kanth Oct 08 '18 at 01:32
  • I haven't had any problems with this in my project, though I don't specifically use a CountDownLatch. I use other infrastructure, but the result is the same. – Doug Stevenson Oct 08 '18 at 01:44
  • Let me setup a sample project and try it, that way I can figure out if I had anything configured wrong in the current project. – Sasi Kanth Oct 08 '18 at 01:45
  • It's working fine on newly created sample project. But not working on my current project, I must have configured something wrong. I will try to debug it, thanks for the help – Sasi Kanth Oct 08 '18 at 04:46
  • @DougStevenson would u mind sharing the other infrastructure ? :p the better replacement of countdown latch ? – Santanu Sur Mar 30 '20 at 15:43
6

As I see, you are using addListenerForSingleValueEvent(), which means that the listener will read the data precisely once. That means that your onDataChange() method gets triggered with the current value (from the cache if available, otherwise from Firebase servers), and stop listening immediately after that. In this case there is no need to remove the listener. The only time addListenerForSingleValueEvent needs to be canceled is if there is no network connection when you attach it and the client doesn't have a local copy of the data, either because there was another active listener or because it has a copy of the data on disk.

If you want to keep listening for changes, you should use addValueEventListener(). Using this kind of listener means that your onDataChange() method gets called immediately with the current data, but (unlike addListenerForSingleValueEvent) the listener will stay active after that and your onDataChange() will be called for later changes too.

You can use addValueEventListener() after an Android application is closed, by not removing it. Normally, once you are using a listener, you also need to remove it according to the life-cycle of the activity. If do not remove the listener, the app will work only for a shot period of time because Android will stop your service if the app is not in the foreground. It does this to save resources when the app isn't being used. It also might stop your app from doing any networking, or even kill the app process completely. There's nothing you can do to prevent this, other than making it a foreground service, as you already mentioned.

A foreground service is probably not the best thing to do for your case, nor is it the best thing for your users. Read more about limitations on background services.

My recommendation is to use Firebase Cloud Messaging to notify your app when something has changed that it might be interested in. So your users will recieve notification even if they will keep their app closed.

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
  • It doesn't matter what event listener I use. It seems to act that way intentionally. Yeah I am using fcm already, I am just trying a small workaround when there is no network so that app can fetch from cache. – Sasi Kanth Oct 03 '18 at 15:42
  • Ok, so go ahead with the FCM. That's the best choice. – Alex Mamo Oct 03 '18 at 15:44
  • Yeah, I cannot accept this as answer though. Because it doesn't answer the question I was asking. I wanted to know the underlying cause for event listener not working in that particular case. – Sasi Kanth Oct 03 '18 at 15:46
  • Have you tried to use `addValueEventListener()`? What is the behaviour in that case? – Alex Mamo Oct 03 '18 at 15:47
  • The event listeners are not triggering basically. It's must be the way they intended it to work. – Sasi Kanth Oct 03 '18 at 15:49
  • If you have no internet connection do you get the data from cache? – Alex Mamo Oct 03 '18 at 15:50
  • I think you don't understand what I am trying to say or the content of the question. Basically the event listeners are working fine in the worker when the app is in foreground/background. But once app is killed it's not working anymore, essentially even listener is not getting triggered. So it's the event listener I am using that is at fault. But it seems to be work that way, I wanted to know if there is any other way or a way to trigger it. – Sasi Kanth Oct 03 '18 at 15:52
  • 1
    I understand now. Yes, that's the way it supposed to work and unfortunately you cannot change this behaviour. – Alex Mamo Oct 03 '18 at 15:59
  • I am not trying to be rude. But the above answer doesn't answer the question. It gives a overview about the listeners. – Sasi Kanth Oct 04 '18 at 07:11