Flutterfire: [firebase_messaging] Static variables and fields are being reinitiated when accessed from backgroundmessageHandler

Created on 25 Jan 2020  Â·  8Comments  Â·  Source: FirebaseExtended/flutterfire

Describe the bug
I've done everything as described in the documentation step by step.
But since the background handler should be a static function or top-level. I've tried to use static fields on some class. or even top-level variables. But it's weird that when i access them from inside the handler they are null. I've also used GetIt and inside the handler, it has 0 objects registered (because it uses a static field ).

I've searched on the issue and found a single thing. that the imports to the main.dart file must be absolute (which is not my case). Even here I didn't find an issue dealing with this.

I wonder if that's a problem with dart/flutter eco-system?
Or it's because firebase config references a static handler which implies some kind of isolation?

It's very annoying. the background handler is almost useless if I can't access anything from it.

To Reproduce:
Steps to reproduce the behavior:

  1. Do the steps to enable background messaging in a new project
  2. put some static field in any class you want
  3. move the application to the background and send some message
  4. debug the handler function and you find the field is always null when accessed from inside the handler (it's not null when accessed from other areas in the code).

Expected behavior:
Static variables should be accessible. OR at least it should be mentioned in the documentation

flutter doctor -v:

[√] Flutter (Channel stable, v1.12.13+hotfix.5, on Microsoft Windows [Version 10.0.18362.592], locale en-US)
    • Flutter version 1.12.13+hotfix.5 at C:\Program Files\flutter
    • Framework revision 27321ebbad (7 weeks ago), 2019-12-10 18:15:01 -0800
    • Engine revision 2994f7e1e6
    • Dart version 2.7.0


[√] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
    • Android SDK at C:\Users\Crush\AppData\Local\Android\sdk
    • Android NDK location not configured (optional; useful for native profiling support)
    • Platform android-29, build-tools 29.0.2
    • Java binary at: C:\Users\Crush\AppData\Local\JetBrains\Toolbox\apps\AndroidStudio\ch-0\191.6010548\jre\bin\java
    • Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b03)
    • All Android licenses accepted.

[√] Android Studio (version 3.5)
    • Android Studio at C:\Users\Crush\AppData\Local\JetBrains\Toolbox\apps\AndroidStudio\ch-0\191.6010548
    • Flutter plugin version 41.1.2
    • Dart plugin version 191.8593
    • Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b03)

[!] Connected device
    ! No devices available

crowd messaging bug

Most helpful comment

I'm facing the same issue. I've created a singleton and a static method inside it as a handler for the background message. But when the static method tries to access the singleton instance, it returns null. Here is the sample code:

class NotificationService {

  static NotificationService _instance;

  final FirebaseMessaging _firebase;

  static NotificationService get instance => _instance;

  NotificationService._internal() : this._firebase = FirebaseMessaging();

  factory NotificationService() {
    if (null == _instance) {
      _instance = NotificationService._internal();
      _instance._firebase.configure(
          onBackgroundMessage: NotificationService.staticHandler
      );
    }

    return _instance;
  }

  static Future<dynamic> staticHandler(Map<String, dynamic> msg) {
    print("Static Func >>> $msg"); // Successfully prints
    return NotificationService.instance.instanceFunc(msg); // Fails here, complaining that it's being invoked on null.
  }

  Future<dynamic> instanceFunc(Map<String, dynamic> msg) {
    print("Instance Func >>> $msg");
  }

  void myVarFunc() {
    print("This is my var func");
  }
}

The NotificationService is instantiated from main.dart:

import 'package:myProject/services/notification/notification_service.dart';

run(MyApp());

class MyApp extends StatelessWidget {
   MyApp() {
       final NotificationService _ns = NotificationService();
       NotificationService.instance.myVarFunc(); // Prints successfully.
       . . .
       . . .
   }
}

The following are the logs:

I/flutter ( 6935): Static Func >>> {data: {title: Title_is_here, message: Message_is_here}}
I/flutter ( 6935): Unable to handle incoming background message.
I/flutter ( 6935): NoSuchMethodError: The method 'instanceFunc' was called on null.
I/flutter ( 6935): Receiver: null
I/flutter ( 6935): Tried calling: instanceFunc(_LinkedHashMap len:1)

From what I understand, the onBackgroudMessage handler is in a different isolate and thus has no access to the data from the main isolate. Hence everything is pretty much null.

Any work around for this issue? Is it a good solution to use EventChannels or write it to database for shared access or pass messages to other isolates? Please suggest.

I think I have a solution for this, but I'm not really sure. Please let me know.

So, the idea is to pass the received background data from the secondary isolate to the main isolate. Correct me if I'm wrong, I think the background data handling process has it's own memory space and has no access to the main's. Hence all the initialised objects in the main isolate were null in this secondary isolate.

In the factory NotificationService constructor from my above example, I add this in the if condition where _instance is instantiated:

      ReceivePort rp = ReceivePort();
      SendPort sp = rp.sendPort;
      IsolateNameServer.registerPortWithName(sp, "f12345");
      rp.listen((v) {
        print("RP >>>  $v"); // Successfully prints
        NotificationService.instance.myVarFunc(); // Successfully prints
      }); 

And in the staticHandler method, I add this:

    SendPort sp = IsolateNameServer.lookupPortByName("f12345");
    sp.send(msg);

I first create a ReceiverPort. Then, create a SendPort from this receiver port. Now the receiver port can listen to any incoming messages that are sent to this send-port. To ID this send-port, I register it in the IsolateNameServer as "f12345".When the onBackgroundMessage handler is triggered, I send this message to the send-port with name "f12345".

The solution, of course, has more to do with Dart than Flutter. This seems to be working. But I'm yet to test it thoroughly. Please let me know if this is an efficient way to handle...Would also like to know if this a heavy operation.

All 8 comments

Hi @MajedDH
can you please provide your flutter run --verbose
and your pubspec.yaml
or if possible a reproducible minimal code sample.
Thank you

I was able to reproduce this bug on a brand new empty project, just the counter sample app: Here the project includes a google-services.json file (I can give you access to the project if you want). and here is a sample of the flutter run -v of this project: runoutput.txt



flutter doctor -v output

[√] Flutter (Channel stable, v1.12.13+hotfix.7, on Microsoft Windows [Version 10.0.18362.592], locale en-US)
• Flutter version 1.12.13+hotfix.7 at C:\Program Files\flutter
• Framework revision 9f5ff2306b (7 days ago), 2020-01-26 22:38:26 -0800
• Engine revision a67792536c
• Dart version 2.7.0

[√] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
• Android SDK at C:\Users\Crush\AppData\Local\Android\sdk
• Android NDK location not configured (optional; useful for native profiling support)
• Platform android-29, build-tools 29.0.2
• Java binary at: C:\Users\Crush\AppData\Local\JetBrains\Toolbox\apps\AndroidStudio\ch-0\191.6010548\jre\bin\java
• Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b03)
• All Android licenses accepted.

[√] Android Studio (version 3.5)
• Android Studio at C:\Users\Crush\AppData\Local\JetBrains\Toolbox\apps\AndroidStudio\ch-0\191.6010548
• Flutter plugin version 41.1.2
• Dart plugin version 191.8593
• Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b03)

[√] Connected device (1 available)
• Android SDK built for x86 • emulator-5554 • android-x86 • Android 10 (API 29) (emulator)

• No issues found!

You can notice in the last few lines, I've sent the same message, the foreground one arrived with no problems, but the background one crashed because of the null exception. Even if you receive messages in the foreground after the null error in the background you can see the variables have preserved their values (the _x counter doesn't become null).

I've noticed that the background queue is processed in a separate dart isolate, I know about isolates in dart ( very little), and they don't share variables. And as far as I know, you can move data in between isolates only using event messages. Maybe that's the issue?

I'm facing the same issue. I've created a singleton and a static method inside it as a handler for the background message. But when the static method tries to access the singleton instance, it returns null. Here is the sample code:

class NotificationService {

  static NotificationService _instance;

  final FirebaseMessaging _firebase;

  static NotificationService get instance => _instance;

  NotificationService._internal() : this._firebase = FirebaseMessaging();

  factory NotificationService() {
    if (null == _instance) {
      _instance = NotificationService._internal();
      _instance._firebase.configure(
          onBackgroundMessage: NotificationService.staticHandler
      );
    }

    return _instance;
  }

  static Future<dynamic> staticHandler(Map<String, dynamic> msg) {
    print("Static Func >>> $msg"); // Successfully prints
    return NotificationService.instance.instanceFunc(msg); // Fails here, complaining that it's being invoked on null.
  }

  Future<dynamic> instanceFunc(Map<String, dynamic> msg) {
    print("Instance Func >>> $msg");
  }

  void myVarFunc() {
    print("This is my var func");
  }
}

The NotificationService is instantiated from main.dart:

import 'package:myProject/services/notification/notification_service.dart';

run(MyApp());

class MyApp extends StatelessWidget {
   MyApp() {
       final NotificationService _ns = NotificationService();
       NotificationService.instance.myVarFunc(); // Prints successfully.
       . . .
       . . .
   }
}

The following are the logs:

I/flutter ( 6935): Static Func >>> {data: {title: Title_is_here, message: Message_is_here}}
I/flutter ( 6935): Unable to handle incoming background message.
I/flutter ( 6935): NoSuchMethodError: The method 'instanceFunc' was called on null.
I/flutter ( 6935): Receiver: null
I/flutter ( 6935): Tried calling: instanceFunc(_LinkedHashMap len:1)

From what I understand, the onBackgroudMessage handler is in a different isolate and thus has no access to the data from the main isolate. Hence everything is pretty much null.

Any work around for this issue? Is it a good solution to use EventChannels or write it to database for shared access or pass messages to other isolates? Please suggest.

I'm facing the same issue. I've created a singleton and a static method inside it as a handler for the background message. But when the static method tries to access the singleton instance, it returns null. Here is the sample code:

class NotificationService {

  static NotificationService _instance;

  final FirebaseMessaging _firebase;

  static NotificationService get instance => _instance;

  NotificationService._internal() : this._firebase = FirebaseMessaging();

  factory NotificationService() {
    if (null == _instance) {
      _instance = NotificationService._internal();
      _instance._firebase.configure(
          onBackgroundMessage: NotificationService.staticHandler
      );
    }

    return _instance;
  }

  static Future<dynamic> staticHandler(Map<String, dynamic> msg) {
    print("Static Func >>> $msg"); // Successfully prints
    return NotificationService.instance.instanceFunc(msg); // Fails here, complaining that it's being invoked on null.
  }

  Future<dynamic> instanceFunc(Map<String, dynamic> msg) {
    print("Instance Func >>> $msg");
  }

  void myVarFunc() {
    print("This is my var func");
  }
}

The NotificationService is instantiated from main.dart:

import 'package:myProject/services/notification/notification_service.dart';

run(MyApp());

class MyApp extends StatelessWidget {
   MyApp() {
       final NotificationService _ns = NotificationService();
       NotificationService.instance.myVarFunc(); // Prints successfully.
       . . .
       . . .
   }
}

The following are the logs:

I/flutter ( 6935): Static Func >>> {data: {title: Title_is_here, message: Message_is_here}}
I/flutter ( 6935): Unable to handle incoming background message.
I/flutter ( 6935): NoSuchMethodError: The method 'instanceFunc' was called on null.
I/flutter ( 6935): Receiver: null
I/flutter ( 6935): Tried calling: instanceFunc(_LinkedHashMap len:1)

From what I understand, the onBackgroudMessage handler is in a different isolate and thus has no access to the data from the main isolate. Hence everything is pretty much null.

Any work around for this issue? Is it a good solution to use EventChannels or write it to database for shared access or pass messages to other isolates? Please suggest.

Update:
Tried using EventChannels, but failed with this error:
E/flutter (17854): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: MissingPluginException(No implementation found for method on channel

I'm facing the same issue. I've created a singleton and a static method inside it as a handler for the background message. But when the static method tries to access the singleton instance, it returns null. Here is the sample code:

class NotificationService {

  static NotificationService _instance;

  final FirebaseMessaging _firebase;

  static NotificationService get instance => _instance;

  NotificationService._internal() : this._firebase = FirebaseMessaging();

  factory NotificationService() {
    if (null == _instance) {
      _instance = NotificationService._internal();
      _instance._firebase.configure(
          onBackgroundMessage: NotificationService.staticHandler
      );
    }

    return _instance;
  }

  static Future<dynamic> staticHandler(Map<String, dynamic> msg) {
    print("Static Func >>> $msg"); // Successfully prints
    return NotificationService.instance.instanceFunc(msg); // Fails here, complaining that it's being invoked on null.
  }

  Future<dynamic> instanceFunc(Map<String, dynamic> msg) {
    print("Instance Func >>> $msg");
  }

  void myVarFunc() {
    print("This is my var func");
  }
}

The NotificationService is instantiated from main.dart:

import 'package:myProject/services/notification/notification_service.dart';

run(MyApp());

class MyApp extends StatelessWidget {
   MyApp() {
       final NotificationService _ns = NotificationService();
       NotificationService.instance.myVarFunc(); // Prints successfully.
       . . .
       . . .
   }
}

The following are the logs:

I/flutter ( 6935): Static Func >>> {data: {title: Title_is_here, message: Message_is_here}}
I/flutter ( 6935): Unable to handle incoming background message.
I/flutter ( 6935): NoSuchMethodError: The method 'instanceFunc' was called on null.
I/flutter ( 6935): Receiver: null
I/flutter ( 6935): Tried calling: instanceFunc(_LinkedHashMap len:1)

From what I understand, the onBackgroudMessage handler is in a different isolate and thus has no access to the data from the main isolate. Hence everything is pretty much null.

Any work around for this issue? Is it a good solution to use EventChannels or write it to database for shared access or pass messages to other isolates? Please suggest.

I think I have a solution for this, but I'm not really sure. Please let me know.

So, the idea is to pass the received background data from the secondary isolate to the main isolate. Correct me if I'm wrong, I think the background data handling process has it's own memory space and has no access to the main's. Hence all the initialised objects in the main isolate were null in this secondary isolate.

In the factory NotificationService constructor from my above example, I add this in the if condition where _instance is instantiated:

      ReceivePort rp = ReceivePort();
      SendPort sp = rp.sendPort;
      IsolateNameServer.registerPortWithName(sp, "f12345");
      rp.listen((v) {
        print("RP >>>  $v"); // Successfully prints
        NotificationService.instance.myVarFunc(); // Successfully prints
      }); 

And in the staticHandler method, I add this:

    SendPort sp = IsolateNameServer.lookupPortByName("f12345");
    sp.send(msg);

I first create a ReceiverPort. Then, create a SendPort from this receiver port. Now the receiver port can listen to any incoming messages that are sent to this send-port. To ID this send-port, I register it in the IsolateNameServer as "f12345".When the onBackgroundMessage handler is triggered, I send this message to the send-port with name "f12345".

The solution, of course, has more to do with Dart than Flutter. This seems to be working. But I'm yet to test it thoroughly. Please let me know if this is an efficient way to handle...Would also like to know if this a heavy operation.

@RaD0 how about you apply your solution in it's simplest way to the minimal example I've provided?
Here: https://github.com/MajedDH/firebase_background_bug_sample

It's really an empty project with only firebase messaging just to demonstrate the problem.

Hi! Any updates on this? Still struggling here with sleepless nights trying to get this to work :P

I'm trying to use the background message handler to bring window to front, however I get the same error above that the function is not found from within the background listener

Facing same issue. It's quite a severe issue.
Need to access already initialised method channel, to pass the remoteMessage to received in backgroundMessageHandler, but its always null, whereas the same channel is found initialised in the onMessage callback - foreground.
Can someone suggest some fix?

Was this page helpful?
0 / 5 - 0 ratings