简体   繁体   中英

Flutter - Firebase Cloud Messaging Navigation in onLaunch doesn't work

I am building an app which receives push notifications using FCM.

I want to route to a specific screen when a notification is clicked (for example, the user's profile).

On Android, it works perfectly fine when the app is just closed (and not "killed"), but when the app is terminated ("killed") it is not working. On iOS, it doesn't work at all.

I am implementing it life this:

NotificationsHandler :

class NotificationsHandler {
  static final NotificationsHandler instance = NotificationsHandler();

  final _fcm = FirebaseMessaging();

  void onBackgroundNotificationRecevied({Function onReceived}) {
    _fcm.configure(
      onResume: (message) => onReceived(message),
      onLaunch: (message) => onReceived(message),
    );
  }
}

myMainScreen's initState :

@override
  void initState() {
    NotificationsHandler.instance.onBackgroundNotificationRecevied(
      onReceived: (message) async {
        final userId = message['data']['userId'];
        final user = this.users.firstWhere((currentUser) => currentUser.id == userId);

        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => UserProfileScreen(
              user,
            ),
          ),
        );
      }
    );
    super.initState();
  }

Code for sending the notifications (through an external React admin panel) :

const payload = {
    notification: {
        title: `myTitle`,
        body: `My message`,
        sound: "default",
        badge: "1",
        click_action: "FLUTTER_NOTIFICATION_CLICK",
    },
    data: {
        click_action: 'FLUTTER_NOTIFICATION_CLICK',
        userId: myUserId,
    },
};
    
const options = {
    priority: 'high',
    timeToLive: 60 * 60 * 24
};

admin.messaging().sendToTopic('myTopic', payload, options);

Does anyone know why it isn't working?

Thank you!

You can try to use getInitialMessage instead of onLaunch . I believe this will do what you want as documentation indicated the following lines:

This should be used to determine whether specific notification interaction should open the app with a specific purpose (eg opening a chat message, specific screen etc).

@override
void initState() {
  super.initState();
  FirebaseMessaging.instance.getInitialMessage().then((RemoteMessage message) {
    if (message != null) {
      Navigator.pushNamed(context, '/message', arguments: MessageArguments(message, true));
    }
  });
}

I assume that you're using firebase_messaging package.

iOS

If you're testing it on simulator, it won't work. It's stated in the documentation that:

FCM via APNs does not work on iOS Simulators. To receive messages & notifications a real device is required.

Android

On Android, if the user force quits the app from device settings, it must be manually reopened again for messages to start working.

More info here .

Based on my experience, I remember that onLaunch Callback function fires right after execute main function, even before the initstate method.

What I did was locate service class using service locator(eg get_it) at main function before runApp() then onLaunch Callback set initial configuration so that your App can use it's value.

For example

final getIt = GetIt.instance;

Future<void> main() async {
    WidgetsFlutterBinding.ensureInitialized();
    await Firebase.initializeApp();
    getIt.registerSingleton<Configurator>(Configurator());///start configuration service
    FirebaseMessagingService.initialise()///start firebase messaging service
    runApp();
}

...

class FirebaseMessagingService {
  final FirebaseMessaging _fcm;

  FirebaseMessagingService.initialise() : _fcm = FirebaseMessaging() {
    if (Platform.isIOS) {
      _fcm.requestNotificationPermissions(IosNotificationSettings());
    }

    _fcm.configure(
      ...
      onLaunch: _launchMessageHandler,
    );
  }
}

//top-level function or static method
_launchMessageHandler(Map<String, dynamic> message) async {
  //some parsing logic
  ...
  getIt<Configurator>().setInitialConfig(parsed_data);
}


...

//then

void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      final config = getIt<Configurator>().config;
      //do something
    }};

You will have to implement those whole settings but it's flow is like above roughly.

I assume your trouble is more towards navigating to another screen upon clicking the notification.

If that is the case create a class for routing.

an example would be as below:

class Navigator{
  GlobalKey<NavigatorState> _navigator;

  /// Singleton getter
  static Navigator get instance => _instance ??= Navigator._();

  /// Singleton Holder
  static Navigator _instance;

  /// Private Constructor
  Navigator._() {
    _navigator = GlobalKey<NavigatorState>();
  }

  GlobalKey<NavigatorState> get navigatorKey => _navigator;

  Future<dynamic> navigateTo(String routeName, [dynamic arguments]) =>
      navigatorKey.currentState.pushNamed(routeName, arguments: arguments);

Now comes the screen/pages

class CustomRoutes {
  const CustomRoutes._();

  factory CustomRoutes() => CustomRoutes._();

  static const String HomeRoute = 'HomeRoute';
    ...
    ...

  static Route<dynamic> generateRoute(RouteSettings settings) {
    switch (settings.name) {
      case CustomRoutes.HomeRoute:
        return MaterialPageRoute(builder: (_) => HomePage());
      default:
        return MaterialPageRoute(
            builder: (_) => Scaffold(
                body: Center(child: Text('No path for ${settings.name}'))));
    }
  }
}

So if u wish to go to HomePage you can just invoke

await Navigator.instance.navigateTo(CustomRoutes.HomeRoute, someArguments)

Do remember to register the globalkey to your materialapp

MaterialApp(
...
...
navigatorKey: Navigator.instance.navigatorKey
...);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM