firebase-android-sdk icon indicating copy to clipboard operation
firebase-android-sdk copied to clipboard

Bug/Feature Request: Update FirebaseMessagingService to extend from LifecycleService

Open ColtonIdle opened this issue 2 years ago • 16 comments

[READ] Step 1: Are you in the right place?

Issues filed here should be about bugs in the code in this repository. If you have a general question, need help debugging, or fall into some other category use one of these other channels:

  • For general technical questions, post a question on StackOverflow with the firebase tag.
  • For general Firebase discussion, use the firebase-talk google group.
  • For help troubleshooting your application that does not fall under one of the above categories, reach out to the personalized Firebase support channel.

[REQUIRED] Step 2: Describe your environment

Firebase android sdk

[REQUIRED] Step 3: Describe the problem

Currently the FirebaseMessagingService needs to make a call to a server to update when a token is refreshed. This is typically done in the modern way using kotlin + coroutines, but it's tough to launch a suspend function without a lifecycle aware scope. LifecycleService is the solution for this. Can we update FMS to extend from this instead?

See: https://stackoverflow.com/questions/68156680/how-to-start-a-background-thread-with-coroutine-on-a-service

ColtonIdle avatar Jun 29 '22 01:06 ColtonIdle

I couldn't figure out how to label this issue, so I've labeled it for a human to triage. Hang tight.

google-oss-bot avatar Jun 29 '22 01:06 google-oss-bot

Hey @ColtonIdle Thanks for bringing this up. Our engineers will have a look and discuss this further once they have the time.

For the time being, if you'd like to make your FirebaseMessagingService lifecycle-aware, you can actually implement a LifecycleOwner and implement a few methods:

class MyFirebaseMessagingService : FirebaseMessagingService(), LifecycleOwner {

    private val mDispatcher = ServiceLifecycleDispatcher(this)

    override fun onCreate() {
        mDispatcher.onServicePreSuperOnCreate()
        super.onCreate()
    }

    override fun onStart(intent: Intent?, startId: Int) {
        mDispatcher.onServicePreSuperOnStart()
        super.onStart(intent, startId)
    }

    override fun onDestroy() {
        mDispatcher.onServicePreSuperOnDestroy()
        super.onDestroy()
    }

    override fun getLifecycle(): Lifecycle = mDispatcher.lifecycle

    override fun onNewToken(token: String) {
        lifecycleScope.launch {
            sendRegistrationToServer(token)
        }
    }
}

I believe it can work as a temporary workaround. See the full list of changes here: https://github.com/firebase/quickstart-android/commit/9ed12fd91eb0fdafc9477627cce881041eb73388

thatfiredev avatar Jun 30 '22 21:06 thatfiredev

Another way you can launch a coroutine from FirebaseMessagingService is by declaring its scope and cancelling it on the Service's onDestroy():

class MyFirebaseMessagingService : FirebaseMessagingService() {

    private val job = SupervisorJob()
    private val coroutineScope = CoroutineScope(Dispatchers.Default + job)

    override fun onDestroy() {
        coroutineScope.cancel()
        super.onDestroy()
    }

    override fun onNewToken(token: String) {
        coroutineScope.launch {
            sendRegistrationToServer(token)
        }
    }
}

I feel like this is much less verbose than having to deal with lifecycle events, if your main goal is to launch a coroutine.

thatfiredev avatar Jul 01 '22 15:07 thatfiredev

Thanks, the temporary workarounds are appreciated. Looking forward to when cororutines can be launched natively inside of FirebaseMessagingService.

ColtonIdle avatar Jul 02 '22 05:07 ColtonIdle

Hello sir

I need your help Space between lines of poems data in firebase database using \n is not working! I want to display poems like this in my app from firebase database. This is my home This is my home This is my home This is my home Using \n not working Can you help me? [email protected] Thanks

Mohammadbasharsahak avatar Jul 05 '22 11:07 Mohammadbasharsahak

Hi @Mohammadbasharsahak, could you open a new issue so we can help you with your concern? Thanks!

argzdev avatar Jul 05 '22 12:07 argzdev

What to do if onDestroy calls before coroutine finishes it's job

timkabot avatar Jan 17 '23 19:01 timkabot

I wonder is this solution https://github.com/firebase/firebase-android-sdk/issues/3851#issuecomment-1172482071 still the best one we've got at this point @thatfiredev ? Or has there been any new APIs to give us a coroutine just like WorkManager does lately?

And if this is our best bet still, I wonder, is the Dispatchers.Default necessary when creating this CoroutineScope? What will it use if we do not provide any explicit dispatcher?

I wonder, since the functions are already annotated with @WorkerThread, don't we get guarantees that all of it is running in a background thread anyway, so we can somehow avoid having to deal with creating the coroutine scope ourselves and do something else instead?

StylianosGakis avatar Apr 20 '23 16:04 StylianosGakis

@thatfiredev

I am creating a coroutine scope and cancelling it in onDestroy(). Is there any possibility of onDestroy() being called before the scope completes its job?

Is the onDestroy() called immediately after onMessageReceived() finishes?

Khudoyshukur avatar May 03 '23 13:05 Khudoyshukur

@Khudoyshukur

Yes, FirebaseMessagingService's onDestroy() can be called immediately after it has returned from onMessageReceived() in certain cases, so it probably shouldn't be relied upon to continue work after the message has been handled.

gsakakihara avatar Jun 21 '23 19:06 gsakakihara

@gsakakihara Hello!

Thank you for answering in this issue, it's very useful.

I have some another questions about using coroutines in FCM. If I understand you correctly, I cannot execute some async things inside of onMessageReceived, right?

For example, if I'm doing this way, my coroutine can be killed before it was finished?

    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        Log.d(TAG, "Got push message: ${remoteMessage.rawData.toString()}")

        coroutineScope.launch {
            val res = tryDoSomethingAsync()

            res.collect() { r -> Log.d(TAG, "Finished coroutine")}
        }
    }

So, is there any way to run some long-running async thing after receiving notification inside of onMessageReceived?

rostik404 avatar Aug 23 '23 10:08 rostik404

@rostik404 I was also facing the same issue. Now, I am enqueueing a worker class for long-running async work in onMessageReceived()

Khudoyshukur avatar Aug 23 '23 10:08 Khudoyshukur

@Khudoyshukur, sounds good, thank you

Are you creating instance of WorkManager inside of onMessageReceived? Could you please provide some example?

rostik404 avatar Aug 23 '23 11:08 rostik404

@rostik404

Yes, inside onMessageReceived(). I provide an example:

override fun onMessageReceived(remoteMessage: RemoteMessage) {
    ...
    val workRequest = OneTimeWorkRequestBuilder<YourWorker>().build()
    WorkManager.getInstance(context).enqueue(workRequest)
}

Khudoyshukur avatar Aug 23 '23 11:08 Khudoyshukur

Pretty much what @Khudoyshukur said. Due to the way FCM message handling works on Android, your app is subject to being killed after you return from onMessageReceived and you should finish onMessageReceived within 20 seconds (your app is subject to being killed after that anyway). Anything that could take longer than that should be handled by something like WorkManager.

gsakakihara avatar Aug 23 '23 16:08 gsakakihara

I can't believe this is still not solved... :(

On top of that, the onStart method thatfiredev suggested overriding is deprecated, too, and its replacement onStartCommand is final in EnhancedIntentService.

Khazbs avatar Feb 14 '24 15:02 Khazbs