Skip to content
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

🐛 [firebase_messaging] "No Firebase App..." in background message handler #3520

Closed
rubenvereecken opened this issue Sep 10, 2020 · 7 comments · Fixed by #4012
Closed

🐛 [firebase_messaging] "No Firebase App..." in background message handler #3520

rubenvereecken opened this issue Sep 10, 2020 · 7 comments · Fixed by #4012
Labels
plugin: messaging type: question A question where further information is requested

Comments

@rubenvereecken
Copy link

Bug report

Describe the bug
My background message handler always throws the error

I/flutter (27134): Unable to handle incoming background message.
I/flutter (27134): [core/no-app] No Firebase App '[DEFAULT]' has been created - call Firebase.initializeApp()

Closer inspection of the stacktrace revealed that it threw because it relies on code that relies on the FirebaseAuth.instance.

Steps to reproduce

Steps to reproduce the behavior:

  1. Start app
  2. Send app to background
  3. Send data only message
  4. Witness the background message handler crash

Expected behavior

I would expect to be able to use Firebase in the background message handler. This seems not to be the case and I'd like to figure out what the limitations are. Is this expected behavior? Can I get around it by initializing firebase inside the message handler? Globals don't seem to be shared - a separate thread or isolate then? If this is indeed expected (the docs don't really say as much) - what is the advised way to pass messages to other parts of the app, say so that data can be displayed when it opens again? Or is there no way to communicate back to "the main thread"?


Flutter dependencies

  firebase_core: ^0.5.0
  firebase_analytics: ^6.0.0
  firebase_messaging: ^7.0.0
  firebase_auth: ^0.18.0+1
  firebase_remote_config: ^0.4.0
@rubenvereecken rubenvereecken added Needs Attention This issue needs maintainer attention. type: bug Something isn't working labels Sep 10, 2020
@darshankawar
Copy link

Hi @rubenvereecken,
Do you also get same handler crash when you try to send notification portion also along with data portion when the app is in background ?
When in the background, apps receive the notification payload in the notification tray, and only handle the data payload when the user taps on the notification.

Please see this link for more information and check if it's helpful to answer some of your questions.
Thanks.

@darshankawar darshankawar added blocked: customer-response Waiting for customer response, e.g. more information was requested. and removed Needs Attention This issue needs maintainer attention. type: bug Something isn't working labels Sep 11, 2020
@rahuldubey093
Copy link

Facing the exact same issue. Need to update a sent message's delivery receipt when the notification is delivered. I am leaving notification payload empty and passing all the data with data payload.

Intializing Firebase in main():

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(MyApp());
}

Then from myBackgroundMessageHandler calling _setMessageDelivered() which makes a call to Firestore:

static Future<dynamic> myBackgroundMessageHandler(Map<String, dynamic> message) {
    _setMessageDelivered(message);
    return Future<void>.value();
  }

static Future<void> _setMessageDelivered(Map<String, dynamic> message) async {

   // getting required data from 'message' here...

    Map<String, dynamic> data = {
      'status': 'delivered',
    };

    await FirebaseFirestore.instance
        .collection('sample')
        .doc('sample')
        .set(data, SetOptions(merge: true));
  }

Getting the error:
[ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: [core/no-app] No Firebase App '[DEFAULT]' has been created - call Firebase.initializeApp()

Everything works as expected when the app is in the foreground.

One possible solution can be to check AppLifecycleState.paused and the reinitialize firebase using Firebase.initializeApp() every time a notification is received when the app is in the background but it might not handle cases when the app is terminated and also Firebase documentation mentions to initialize firebase only once.

@rubenvereecken
Copy link
Author

@darshankawar that would work, though it is a very different use case from mine. I very much want to do a bit of background processing rather than responding to the user opening the app again - they might never. Opportunistic processing of course because I know data messages might not come through, but all the same. That's why I'm talking explicitly about data-only messages here. They could be entirely stand-alone without a displayed notification.

@rahuldubey093 It seems like you have a similar use case to mine, meaning that it's probably not such a rare use case. It's a clever workaround to only kick back into gear when the app is un-paused but something tells me that won't work. I have a feeling that the background data message handler is fundamentally different from the foreground data message handler in that it is started in a different "thread", with no shared memory space, as I've seen with Isolates. I'm pretty sure it's an Isolate. If that's the case it means that communication to the rest of your app needs to be done through some other means - some kind of shared messaging queue. But I'm too new to Isolates to know what the best solution is here.

So my question to the developers then: Is my presumption correct? Are background data message handlers run in an Isolate? If so, what is the recommended way to communicate to the rest of the app? Is the rest of the app even still running, if that question even makes sense? So if I manage to get a message from the Isolate to the "main app", could the main app now do a network request for example? Or is the "main app" paused?

@google-oss-bot google-oss-bot added Needs Attention This issue needs maintainer attention. and removed blocked: customer-response Waiting for customer response, e.g. more information was requested. labels Sep 14, 2020
@darshankawar darshankawar added plugin: messaging type: question A question where further information is requested and removed Needs Attention This issue needs maintainer attention. labels Sep 14, 2020
@rahuldubey093
Copy link

One possible solution can be to check AppLifecycleState.paused and the reinitialize firebase using Firebase.initializeApp() every time a notification is received when the app is in the background but it might not handle cases when the app is terminated and also Firebase documentation mentions to initialize firebase only once.

I tried it but it doesn't work, there are some other errors. @rubenvereecken looks like what you said about isolates is true, when executing code inside myBackgroundMessageHandler() it seems like the code is executed independently and has only access to whatever is inside the myBackgroundMessageHandler() method.

When I figured this out, I thought maybe accessing Cloud Firestore via the REST API will work, so I tried it and it works! So you can perform operations on the database using the REST API via HTTP calls.

My _setMessageDelivered() method looks like this now. I am performing REST API PATCH to update selective fields in the database:

static Future<void> _setMessageDelivered(Map<String, dynamic> message) async {
    
   // getting required data from 'message' here...

    await http.patch(
      'https://firestore.googleapis.com/v1/<.........YOUR_PROJECT_PATH_HERE.........>&updateMask.fieldPaths=status',
      headers: <String, String>{
        'Content-Type': 'application/json',
        'Authorization': <YOUR_KEY>, 
      },
      body: jsonEncode(<String, dynamic>{
        'fields': <String, dynamic>{
          "status": <String, dynamic>{"stringValue": "delivered"},
        },
      })
    );
  }

Maybe it can help as a temporary solution.

@rubenvereecken
Copy link
Author

@rahuldubey093 That's really neat and thanks for confirming part of my suspicion! I'll still be on the hunt for a solution to communicate to the rest of the app as one of the main consequences of data messages is pulling in data or doing something on the background. I'll see if there's a way to communicate in-memory especially but perhaps I can fold it into our persistent storage.

@ghenry
Copy link

ghenry commented Nov 1, 2020

I'm here too and figured it must be an isolate. Was discussing this here with some ideas:

MaikuB/flutter_local_notifications#880 (comment)

@mdalihusain
Copy link

mdalihusain commented Nov 3, 2020

@rahuldubey093 That's really neat and thanks for confirming part of my suspicion! I'll still be on the hunt for a solution to communicate to the rest of the app as one of the main consequences of data messages is pulling in data or doing something on the background. I'll see if there's a way to communicate in-memory especially but perhaps I can fold it into our persistent storage.

I ve managed to run firestore in the background message handler, by registering the Firebase core and Firestore plugins in Application.kt and then initializing Firebase in the background message handler isolate.

Here is the code for the respected files -
Application.kt

package com.example.yourpackagename

import io.flutter.app.FlutterApplication
import io.flutter.plugin.common.PluginRegistry
import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback
import io.flutter.plugins.GeneratedPluginRegistrant
import io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService
import io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin
import io.flutter.plugins.firebase.core.FlutterFirebaseCorePlugin
import io.flutter.plugins.firebase.firestore.FlutterFirebaseFirestorePlugin

class Application : FlutterApplication(), PluginRegistrantCallback {
    override fun onCreate() {
        super.onCreate()
        FlutterFirebaseMessagingService.setPluginRegistrant(this);
    }
    override fun registerWith(registry: PluginRegistry?) {
        io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin.registerWith(registry?.registrarFor("io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin"));
        io.flutter.plugins.firebase.core.FlutterFirebaseCorePlugin.registerWith(registry?.registrarFor("io.flutter.plugins.firebase.core.FlutterFirebaseCorePlugin"));
        io.flutter.plugins.firebase.firestore.FlutterFirebaseFirestorePlugin.registerWith(registry?.registrarFor("io.flutter.plugins.firebase.firestore.FlutterFirebaseFirestorePlugin"));
        
//This is optional if you want to use shared Preferences as well
io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin.registerWith(registry?.registrarFor("io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin"));
    }
}

and then wherever you have declared the callbacks for push notification manager. main.dart or notifications.dart -

Future<dynamic> myBackgroundMessageHandler(Map<String, dynamic> message) async {
  print("myBackgroundMessageHandler fired");
  if (message.containsKey('data')) {
    // Handle data message
    final dynamic data = message['data'];
    await Firebase.initializeApp();
    print("Firebase initialized");
    FirebaseFirestore firebaseFirestore = FirebaseFirestore.instance;
    //Do Anything with firestore now
    //Similarly any other flutter fire plugin can be used here after registering it in the Application.kt
      }
}

@firebase firebase locked and limited conversation to collaborators Dec 4, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
plugin: messaging type: question A question where further information is requested
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants