Closed elenaconache closed 1 year ago
@elenacrst Thanks for the detailed report. From the code sample you shared above, I see that you are using dio
which is third party plugin and also seem to be using third party api (pipedream). But in order to properly narrow down the behavior to firebase_messaging
plugin only, since, we specifically only support messages received from the Firebase APIs as we cannot guarantee that messages received from third party packages will not have any unintended side-effects on other Firebase products such as messaging delivery reporting and Analytics data.
To help us triage and locate genuine issues in the plugin, please provide minimal code sample without any third party plugins or api usage so that we can take a look at it further.
@darshankawar Here is the updated code, using some Text widgets to display the data for each call. I removed pipedream and dio.
import 'dart:async';
import 'dart:io';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
String? backgroundHandlerMessage;
String? foregroundHandlerMessage;
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
backgroundHandlerMessage = message.data.toString();
}
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
runApp(MessagingExampleApp());
}
class MessagingExampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Messaging Example App',
theme: ThemeData.dark(),
routes: {
'/': (context) => Application(),
},
);
}
}
class Application extends StatefulWidget {
@override
State<StatefulWidget> createState() => _Application();
}
class _Application extends State<Application> {
String? initialMessage;
String? delayInitialMessage;
String? openedAppMessage;
bool calledOpenedApp = false;
bool calledInitialMessageWithDelay = false;
bool calledInitialMessage = false;
@override
void initState() {
super.initState();
setupInteractedMessage(withDelay: false);
Future.delayed(Duration(seconds: 6)).then((value) {
setupInteractedMessage(withDelay: true);
});
FirebaseMessaging.onMessageOpenedApp.listen((message) {
setState(() {
openedAppMessage = message.data.toString();
calledOpenedApp = true;
});
});
FirebaseMessaging.onMessage.listen(
(event) {
foregroundHandlerMessage = event.data.toString();
if (mounted) {
setState(() {});
}
},
);
}
Future<void> setupInteractedMessage({required bool withDelay}) async {
if (withDelay) {
delayInitialMessage =
(await FirebaseMessaging.instance.getInitialMessage())
?.data
.toString();
setState(() {
calledInitialMessageWithDelay = true;
});
} else {
initialMessage = (await FirebaseMessaging.instance.getInitialMessage())
?.data
.toString();
calledInitialMessage = true;
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Cloud Messaging'),
),
body: SingleChildScrollView(
child: Column(
children: [
MetaCard('Permissions', Permissions()),
Text('called getInitialMessage() : $calledInitialMessage'),
Text('result of getInitialMessage() : $initialMessage'),
Text(
'called getInitialMessage() after delay : $calledInitialMessageWithDelay'),
Text(
'result of getInitialMessage() after delay: $delayInitialMessage'),
Text('called onMessageOpenedApp : $calledOpenedApp'),
Text('result of onMessageOpenedApp listener : $openedAppMessage'),
Text('background handler message : $backgroundHandlerMessage'),
Text('foreground handler message: $foregroundHandlerMessage')
],
),
),
);
}
}
class MetaCard extends StatelessWidget {
final String _title;
final Widget _children;
MetaCard(this._title, this._children);
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
margin: const EdgeInsets.only(left: 8, right: 8, top: 8),
child: Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Container(
margin: const EdgeInsets.only(bottom: 16),
child: Text(_title, style: const TextStyle(fontSize: 18)),
),
_children,
],
),
),
),
);
}
}
class Permissions extends StatefulWidget {
@override
State<StatefulWidget> createState() => _Permissions();
}
class _Permissions extends State<Permissions> {
bool _requested = false;
bool _fetching = false;
late NotificationSettings _settings;
Future<void> requestPermissions() async {
setState(() {
_fetching = true;
});
final settings = await FirebaseMessaging.instance.requestPermission(
announcement: true,
carPlay: true,
criticalAlert: true,
);
await FirebaseMessaging.instance.subscribeToTopic('fcm_test');
setState(() {
_requested = true;
_fetching = false;
_settings = settings;
});
}
Future<void> checkPermissions() async {
setState(() {
_fetching = true;
});
final settings = await FirebaseMessaging.instance.getNotificationSettings();
setState(() {
_requested = true;
_fetching = false;
_settings = settings;
});
}
Widget row(String title, String value) {
return Container(
margin: const EdgeInsets.only(bottom: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('$title:', style: const TextStyle(fontWeight: FontWeight.bold)),
Text(value),
],
),
);
}
@override
Widget build(BuildContext context) {
if (_fetching) {
return const CircularProgressIndicator();
}
if (!_requested) {
return ElevatedButton(
onPressed: requestPermissions,
child: const Text('Request Permissions'));
}
return Column(children: [
row('Authorization Status', statusMap[_settings.authorizationStatus]!),
if (Platform.isIOS == TargetPlatform.iOS) ...[
row('Alert', settingsMap[_settings.alert]!),
row('Announcement', settingsMap[_settings.announcement]!),
row('Badge', settingsMap[_settings.badge]!),
row('Car Play', settingsMap[_settings.carPlay]!),
row('Lock Screen', settingsMap[_settings.lockScreen]!),
row('Notification Center', settingsMap[_settings.notificationCenter]!),
row('Show Previews', previewMap[_settings.showPreviews]!),
row('Sound', settingsMap[_settings.sound]!),
],
ElevatedButton(
onPressed: checkPermissions, child: const Text('Reload Permissions')),
]);
}
}
const statusMap = {
AuthorizationStatus.authorized: 'Authorized',
AuthorizationStatus.denied: 'Denied',
AuthorizationStatus.notDetermined: 'Not Determined',
AuthorizationStatus.provisional: 'Provisional',
};
const settingsMap = {
AppleNotificationSetting.disabled: 'Disabled',
AppleNotificationSetting.enabled: 'Enabled',
AppleNotificationSetting.notSupported: 'Not Supported',
};
const previewMap = {
AppleShowPreviewSetting.always: 'Always',
AppleShowPreviewSetting.never: 'Never',
AppleShowPreviewSetting.notSupported: 'Not Supported',
AppleShowPreviewSetting.whenAuthenticated: 'Only When Authenticated',
};
The result looks like this:
As you can see, the variable used for the first call to getInitialMessage() stays null, while the one initialized after a delay is not null.
Thanks for the update. Using code sample above, I am able to replicate the behavior. Keeping this issue open for insights from team on expected behavior.
If this would be the expected behavior, is it possible to create a feature request for finding a way to provide the initial message at app launch or via a listener?
For example if on notification tap I'd need to navigate automatically to some screen with the given data, currently I probably have to either:
Is there an alternative to these methods?
Hello @elenacrst, I'm going to investigate this
I can also reproduce
It will be fixed in next version released later today, thanks for the report :)
Still having same problem in "14.1.4" 😭
Bug report
Describe the bug The getInitialMessage() method does not seem reliable, at least on iOS which I'm using for testing this.
Steps to reproduce
Steps to reproduce the behavior:
Expected behavior
I would expect
getInitialMessage()
to work or to have a clear documentation on when I can call it without receiving null. Note: if I close my app, open some other random app on phone, send notification, tap it -> getInitialMessage() returns the correct data from the very first call, which is what I would expect. But if I don't open any other app and just close it and reopen [as detailed in the steps above], the bug is reproduced. Also, if I open some other app before tapping the notification, the background handler is not called. Not sure what the expected behavior for that is.Sample project
Additional context
In my pubspec.yaml I've added under dependencies: firebase_messaging: ^14.0.4 firebase_core: ^2.1.1
I'm not disabling swizzling or anything like that in Info.plist as some docs were not recommending so.
Also, the delay duration for the second getInitialMessage() call is not really fixed, sometimes it worked with 3 seconds, other time with 6 seconds. Do I need to call await on some specific method from Firebase to make sure things are ready for that call? Why does it work after this delay?
On the example project from the firebase_messaging package, I noticed the getInitialMessage() call is done from a button press event, but it would be more useful to be able to call this from app launch, and not require user interaction in order to navigate to the correct screen.
I tested using an iPhone 11 Pro, with iOS 15.7.1
Flutter doctor
Run
flutter doctor
and paste the output below:Click To Expand
``` Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel stable, 3.3.7, on macOS 12.6 21G115 darwin-arm, locale en-RO) [✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0) [✓] Xcode - develop for iOS and macOS (Xcode 14.1) [✓] Chrome - develop for the web [✓] Android Studio (version 2021.2) [✓] Connected device (2 available) [✓] HTTP Host Availability • No issues found! ```Flutter dependencies
Run
flutter pub deps -- --style=compact
and paste the output below:Click To Expand
``` Dart SDK 2.18.4 Flutter SDK 3.3.7