I have an application that is supposed to send the Firebase Token from my Flutter app to an ASP.Net App Server. The endpoint on the app server works - the request from the Flutter app to the App Server is not working.
The reason it is not working is because when I try to send the token, the token doesn't appear to have arrived yet - it's of type Future. How do I turn that token into a string when it finally arrives?
I've tried turning the token directly into a string in the fcmStream.Listen function, I've also tried turning it into a string using _firebaseMessaging.getToken. Neither of them work
FirebaseMessaging _firebaseMessaging = new FirebaseMessaging();
#override
void initState() {
// TODO: implement initState
super.initState();
location.onLocationChanged().listen((value) {
if (this.mounted) {
setState(() {
currentLocation = value;
});
}
});
_firebaseMessaging.configure(
onMessage: (Map<String, dynamic> message) {
print('on message $message');
},
onResume: (Map<String, dynamic> message) {
print('on resume $message');
},
onLaunch: (Map<String, dynamic> message) {
print('on launch $message');
},
);
_firebaseMessaging.requestNotificationPermissions(
const IosNotificationSettings(sound: true, badge: true, alert: true));
String clientToken = _firebaseMessaging.getToken().then((token) {
print("Token Init: " + token.toString());
}
).toString();
BackendService.postToken(clientToken.toString(), "email#gmail.com");
#override
Stream<String> fcmStream = _firebaseMessaging.onTokenRefresh;
fcmStream.listen((token) {
/*print("Token On Refresh: " + token);
BackendService.postToken(token.toString(), "email#gmail.com");*/
}
);
fcmStream.toString();
class BackendService {
static Future<String> postToken(String token, String hostEmail) async {
final responseBody = (await http.get(
'realurlomitted/.../Meets/RegisterDevice?token=$token&hostEmail=$hostEmail')).body;
print(" Response: " + responseBody.toString());
return responseBody.toString();
}
}
Whenever the token.toString prints, it prints the token just fine - I can see that. It just seems like whenever it tries to make the post using http, the token hasn't arrived from whatever getToken is.
If I can turn that Futrure into a string by awaiting it or something, it would solve my problem so that the $token parameter is the token as a string.
More specifically, my request URL should look like:
https://-----/Meets/RegisterDevice?token=c6V49umapn0:Jdsf90832094890s324&hostEmail=email#gmail.com
But it looks like:
https://-----/Meets/RegisterDevice?token=instance of Future<Dynamic>&hostEmail=email#gmail.com
In the Flutter debugger
As you said, awaiting the future will solve your problem. You can write an async function and put the code in your initState inside it and use await, or you can do this:
_firebaseMessaging.getToken().then((token) {
final tokenStr = token.toString();
// do whatever you want with the token here
}
);
This style is now available
FirebaseAuth.instance.currentUser().then((user) {
if (user != null) {
user.getIdToken().then((token) {
Map<dynamic,dynamic> tokenMap = token.claims;
print(tokenMap['sub']);
});
}
});
so this complete code
#override
void initState() {
FirebaseAuth.instance.currentUser().then((user) {
if (user != null) {
user.getIdToken().then((token) {
Map<dynamic,dynamic> tokenMap = token.claims;
print(tokenMap['sub']);
});
}
});
super.initState();
}
Related
Every time i click on push notification it will redirect to desired screen but if I goes to homescreen where i configure fcm it will redirect to that desired screen automatically instead to stay on homescreen
Plugin used - firebase_messaging: ^7.0.3
#override
void onInit() {
// TODO: implement onInit
super.onInit();
//forNotification();
firebaseMessaging.configure(
onMessage: (Map<String, dynamic> message) async {
print("onMessage: $message");
print("onMessage for type: ${message['notification_type']}");
//print("onMessage for title: ${message['aps']['title']}");
//Get.to(ServiceNotificationDetails(message['data']['notification_id']));
/*Get.to(ServiceNotificationDetails(index, serviceNotificationList));*/
},
onLaunch: (Map<String, dynamic> message) async {
print("onLaunch: $message");
print("onMessage for type: ${message['data']['notification_type']}");
moveTo(message, message['data']['notification_type']);
},
onResume: (Map<String, dynamic> message) async {
print("onResume: $message");
print("onMessage for type: ${message['data']['notification_type']}");
},
);
isFinalMethod.value = init.read("petowner");
print("IN OR NOT ");
tabController =
new TabController(length: isFinalMethod.value ? 3 : 2, vsync: this);
setImage();
}
void moveTo(Map<String, dynamic> message,String notification_type)async{
print("Notification type is $notification_type");
switch (notification_type){
case "1":
Get.offAll(ServiceNotificationDetails(message['data']['notification_id']));
break;
case "2":
Get.offAll(PetItemDetails(0,message['data']['project_id']));
break;
case "3":
Get.offAll(ServiceNotificationDetails(message['data']['notification_id']));
break;
}
}
forNotification()async{
if (!initialized) {
firebaseMessaging.requestNotificationPermissions();
firebaseMessaging.configure();
// For testing purposes print the Firebase Messaging token
String token = await firebaseMessaging.getToken();
print("FirebaseMessaging token: $token");
initialized = true;
}
}
Here's the code of fcm config and navigaton method.
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 (e.g. 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(e.g 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
...);
I am trying to send push notification from my php application to my flutter apps via Firebase messaging service. while I start the emulator it receives a token from firebase, then i copy that token and can send message. But I want to store that token to my mysql database once i receive the token. I have 2 scenario. For the first time if user install the apps, i'll insert the token to my database so that i can send message using that token. Now I am confused how to update that token every time the user login(not inserting new token again and again to database).
void setupNotification() async{
_firebaseMessaging.
_firebaseMessaging.getToken().then((token){
//insert the token to user database
saveToken(token);
});
Stream<String> fcmStream = _firebaseMessaging.onTokenRefresh;
fcmStream.listen((token) {
//always update the user database with new token
saveToken(token);
});
_firebaseMessaging.configure(
onMessage: (Map<String, dynamic> message) async{
print("message while app is open: $message");
},
onResume: (Map<String, dynamic> message) async{
print("message: $message");
},
onLaunch: (Map<String, dynamic> message) async{
print("message: $message");
}
);
}
I mean how to identify that the token is new or old(updated) ?
You need to create a Locally stored json file in your app using path_provider plugin.This file will get initialized everytime user opens the app and will contain a user Model containing token of user.On the very first page of the app in initState check if the user is not null.
IF NULL:
Get fcm token and store it in database.After storing it in database write the token in your Locally stored file.
ELSE:
Do nothing
Next time when same user opens the app again in initstate it will check if user is not null.This time it will not add a new token in database as our code to insert token into database won't execute.
Example:
We will store user as follows.
class User {
String token;
String platform;
User({
this.token = '',
this.platform = '',
});
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['token'] = this.token;
data['platform'] = this.platform;
return data;
}
User.fromJson(Map<String, dynamic> json): this(
platform: json["platform"],
token: json["token"],
);
}
Local Data file:
class LocalData {
static User _user;
static User get user => _user;
/// Flag for Authentication.
static bool get isTokenAdded=> _user != null;
static loadData() async {
final file = File((await getApplicationDocumentsDirectory()).path + '/data.json');
try {
final data = jsonDecode(await file.readAsString());
_user = User.fromJson(data['user']);
} catch (e) {
print(e);
}
}
static writeData() async {
final file = File((await getApplicationDocumentsDirectory()).path + '/data.json');
await file.writeAsString(jsonEncode({
'user': _user?.toJson(),
}));
}
static void addToken(User user) {
LocalData._user = user;
writeData();
}
static void removeToken() {
LocalData._user = null;
writeData();
}
}
App's main function:
void main() async {
/// Load Local Data.
WidgetsFlutterBinding.ensureInitialized();
await LocalData.loadData();
runApp(YourApp());
}
Now in initState of your apps first page you can check if token is already added in database by
if(LocalData.isTokenAdded()){
//do nothing
}else{
//get token and insert it into database then write it in file:
LocalData.user.token = token;
LocalData.writeData();
}
I want to play a sound when a notification arrives, but I am not able to play. If the app is opened the sound is played and is working fine. But when the app is killed I see the notification but the sound is not played.
Please help how to do it.
#override
void initState()
{
super.initState();
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
_firebaseMessaging.configure(
onMessage: (Map<String, dynamic> message) async {
print('on message $message');
String key = message["data"]["fcm_call_api"];
if (key == "approval") {}
if(Platform.isAndroid)``
{
_playSound();
}
},
onResume: (Map<String, dynamic> message) async {
},
onLaunch: (Map<String, dynamic> message) async {
_playSound();
},
);
_firebaseMessaging.requestNotificationPermissions(
const IosNotificationSettings(sound: true, badge: true, alert: true));
_firebaseMessaging.onIosSettingsRegistered
.listen((IosNotificationSettings settings) {
});
_firebaseMessaging.getToken().then((String token) {
assert(token != null);
setState(() {
_homeScreenText = "Push Messaging token: $token";
});
});
}
void _playSound() {
AudioCache player = new AudioCache();
const alarmAudioPath = "notification_tone.mp3";
player.play(alarmAudioPath);
}
You did not specify on which platform you experience this problem, but overall background messages with the firebase messaging plugin is very limited right now, but there is a PR actively fixing this: https://github.com/flutter/plugins/pull/1900 (related issue: https://github.com/flutter/flutter/issues/22072)
Right now it is documented behavior that messages arriving in the background are only delivered once it is brought back into foreground (on android). See it's documentation at https://github.com/flutter/plugins/tree/master/packages/firebase_messaging#receiving-messages:
I try to send a message from my website to users with Firebase and store the message in the user device with sqflite plugin.
When the application in the foreground, everything works good and message insert in the database. But when the application in the background or completely close it just displays notification and data not inserted in the database.
edit:
After setting click_action for notification, Clicking on the notification data will be saved but if the user dismisses notification data lost.
code
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
#override
void initState() {
prepareFirebaseCloudMessaging();
super.initState();
}
void prepareFirebaseCloudMessaging() {
if (Platform.isIOS) IOSPermission();
_firebaseMessaging.configure(
onMessage: (Map<String, dynamic> message) async {
notificationDataHandler(message);
},
onResume: (Map<String, dynamic> message) async {
notificationDataHandler(message);
},
onLaunch: (Map<String, dynamic> message) async {
notificationDataHandler(message);
},
);
}
void notificationDataHandler(Map<String, dynamic> message) async {
try {
var databasesPath = await getDatabasesPath();
String path = join(databasesPath, 'MY_DATABASE_FILE.db');
Database database = await openDatabase(path, version: 1,
onCreate: (Database db, int version) async {
await db.execute(
'CREATE TABLE Messages (id INTEGER PRIMARY KEY, message TEXT, date TEXT, seen INTEGER)');
});
Message messageStore = Message(
null, message["data"]["message"], message["data"]["date"], false);
await database.insert("Messages", messageStore.toMap());
database.close();
}catch(e){
debugPrint(e.toString());
}
}