I have a Flutter app that uses Firebase messaging to delivery notifications.
This is the base code, it does nothing special, besides saving the token on my DB.
FirebaseMessaging _firebaseMessaging = new FirebaseMessaging();
_firebaseMessaging.configure(
onMessage: (Map<String, dynamic> message) {
},
onResume: (Map<String, dynamic> message) {
},
onLaunch: (Map<String, dynamic> message) {
},
);
_firebaseMessaging.getToken().then((token) {
saveToken(token);
});
Do I have to implement some kind of background service to keep saving the new token on my DB everytime it gets refreshed? I remember using onTokenRefresh() on Android(JAVA) to do this, but I found nothing about it in Flutter (DART).
I read somewhere that the token gets refreshed every 3600 seconds. I wonder if this is true.
No, FCM token doesn't refresh every 3600 seconds. It only refreshes when :
When user Uninstall/Reinstall the app or Clears App Data
You manually delete FCM Instance using FirebaseMessaging().deleteInstanceID()
You can listen to token refresh stream using:
FirebaseMessaging().onTokenRefresh.listen((newToken) {
// Save newToken
});
Hope it helps
You can use firebaseMessaging.onTokenRefresh to get a stream which receives an event each time a new token is received.
Here is an example of subscribing to the firebaseMessaging.onTokenRefresh stream and updating the token if the token has changed:
FirebaseMessaging().onTokenRefresh.listen((token) async {
final prefs = await SharedPreferences.getInstance();
final String firebaseTokenPrefKey = 'firebaseToken';
final String currentToken = prefs.getString(firebaseTokenPrefKey);
if (currentToken != token) {
print('token refresh: ' + token);
// add code here to do something with the updated token
await prefs.setString(firebaseTokenPrefKey, token);
}
});
You can try with this.. as per new updation
FirebaseMessaging.instance.onTokenRefresh.listen((newToken) {
// Save newToken
});
After the user logs in my app logs her in again automatically every 3500 seconds.
I used a Timer like this:
void _timerPressed() {
const timeout = const Duration(seconds: 3500);
new Timer.periodic(timeout, (Timer t) => _handleSignIn());
}
I set the timer in the 'login' button press method after the login has occurred:
void _loginPressed() {
print('The user wants to login with $_email and $_password');
_handleSignIn()
.then((FirebaseUser user) => print(user))
.catchError((e) => print(e));
_timerPressed();
}
(Don't be fooled by the name of the method, '_timerPressed'. I used a button press for testing the technique and haven't gotten around to renaming the method after I tied it in to the login button.)
Related
I have a flutter app that in some point the administrator users can save one publication.
Now I want all users receive a notification when that publication is posted (with it title, description ..etc).
How could I do that with firebase messaging?
I already wrote this code which, if I go to firebase console and generate a example notification, I receive it normally:
class PushNotificationsManager {
PushNotificationsManager._();
factory PushNotificationsManager() => _instance;
static final PushNotificationsManager _instance = PushNotificationsManager._();
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
Future<void> init() async {
if(Platform.isIOS){
_firebaseMessaging.requestNotificationPermissions(IosNotificationSettings());
}
_firebaseMessaging.configure(
// Called when the app is in the foreground and we receive a push notif.
onMessage: (Map<String, dynamic> message) async {
print("onMessage: ${message}");
print(message['notification']['body']);
},
// Called when the app has been closed completely and its opened
// from the notification directly
onLaunch: (Map<String, dynamic> message) async {
print("onMessage: ${message}");
},
// Called when the app is in the background and its opened from the notif
onResume: (Map<String, dynamic> message) async {
print("onMessage: ${message}");
},
);
}
In summary, how could I generate a notification (with the title and description created) to all users when the admin creates a new publication without going to firebase console to generate it manually?
I'm using firebase_messaging: ^7.0.3
Update
I tried to do this:
Future sendNotification() async {
final String url = 'https://fcm.googleapis.com/fcm/send';
var token = await _firebaseMessaging.getToken();
var data;
data='{"notification": {"body": "this is a body", "title": "this is a title"}, "priority": "high", "data": {"click_action": "FLUTTER_NOTIFICATION_CLICK"}, "to": "${token}"}';
final response = await http.post(
url,
headers: <String, String>{"Content-Type": "application/json", "Keep-Alive" : "timeout=5", "Authorization" : "key=${mykey}"},
body: data
);
print(response.body);
}
...calling this in the method I save the event in firebase only displays the notification to my phone, and not to every phone, there's a way to do it in this form?
You can do this using a cloud function. Either by calling the function from your app when the publication is created, or by having the cloud function listen for a new document. See this Medium post for some ideas: https://medium.com/#jerinamathews/send-firebase-cloud-messaging-fcm-to-a-topic-using-cloud-function-when-realtime-database-value-fa78fa758549
Update
Based on your update, I suggest you look at using a 'Topic' rather than a specific token (that applies to only one device). To use a Topic you need to ensure all users automatically subscribe to the chosen topic when they open the app (they can subscribe to the same topic each time, it has no negative impact). FCM maintains a list of subscribed device tokens against the topic.
I haven't used topic via an http post so I cannot confirm it is possible but I am assuming if you can send to a token you must be able to send to a topic.
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();
}
We are working on an encrypted chat application where we use Firebase Messaging for data notifications. Some client-side logic needs to be done upon receiving a data notification, before showing an actual notification to the user. For example, a phone number will have to be translated to a local contact name. This translation is done by lookup with a map that is already available globally.
The data notifications are received just fine and the onBackgroundMessage callback is called as well. However, it seems impossible to access any kind of state from the onBackgroundMessage function. For example, printing the phone number of the logged in user returns null.
Printing this same global variable from the onMessage callback works just fine.
Running flutter_local_notifications from onMessage works fine, but again, does not work at all from onBackgroundMessage as 'no implementation could be found for the method .show()'. At the moment, it claims that flutterLocalNotificationsPlugin is null, which it isn't really.
It seems to us that onBackgroundMessage has no access to anything the app provides, as soon as the app is backgrounded. Something has to be done to make some of the scope/context available to the background process. For now, that would mainly be the flutter_local_notifications plugin in its entirety, as well as the local contacts list to translate phone number to name.
Has anyone got any idea how to do this?
Here is some of the code:
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
final _chatRepository = ChatRepository();
Future<dynamic> backgroundMessageHandler(Map<String, dynamic> message) async {
if(message.containsKey('data')) {
await _showNotification(message);
return Future<void>.value();
}
}
Future _showNotification(message) async {
List<String> numbers = [];
numbers.add(message['data']['sender']);
var name = await _chatRepository.translatePhoneNumbersToChatName(numbers);
var androidPlatformChannelSpecifics = new AndroidNotificationDetails(
'channel id', 'channel name', 'channel description',
importance: Importance.Max, priority: Priority.High);
var iOSPlatformChannelSpecifics = new IOSNotificationDetails();
var platformChannelSpecifics = new NotificationDetails(
androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.show(
0,
name,
message['data']['body'],
platformChannelSpecifics,
payload: message['data']['body'],
);
}
class NotificationHandler {
final FirebaseMessaging fcm = FirebaseMessaging();
StreamSubscription iosSubscription;
String deviceToken = "";
Future<void> initialize() async {
flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin();
var initializationSettingsAndroid =
new AndroidInitializationSettings('#mipmap/ic_launcher');
var initializationSettingsIOS = new IOSInitializationSettings(onDidReceiveLocalNotification: onDidReceiveLocalNotification);
var initializationSettings = new InitializationSettings(initializationSettingsAndroid, initializationSettingsIOS);
flutterLocalNotificationsPlugin.initialize(initializationSettings, onSelectNotification: onClickNotification);
fcm.configure(
onMessage: (Map<String, dynamic> message) async {
if(message.containsKey('data')) {
print(message);
_showNotification(message);
}
},
onBackgroundMessage: Platform.isIOS
? null
: backgroundMessageHandler,
onLaunch: (Map<String, dynamic> message) async {
if(message.containsKey('data')) {
print(message);
_showNotification(message);
}
},
onResume: (Map<String, dynamic> message) async {
if(message.containsKey('data')) {
print(message);
_showNotification(message);
}
},
);
_updateDeviceToken();
}
.
.
.
Of course, the initialize above is called early on in the application lifecycle.
class NotificationHandler {
static final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); // make it a static field of the class
// ...
}
Future _showNotification(message) async {
// ...
await NotificationHandler.flutterLocalNotificationsPlugin.show( // access it
// ...
);
}
Hope this works for you.
This plugin explains it all better than I could, but it just so happens that the background is a completely different isolate/context and thus it has no access to any plugins if they use an old (pre Flutter 12) API.
https://pub.dev/packages/android_alarm_manager#flutter-android-embedding-v1
Embedding v1 requires you to register any plugins that you want to access from the background. Doing this makes it flutter_local_notifications work properly.
Unfortunately, FCM docs are heavily lacking.
In my Flutter app I use Firebase's phone number authentication as my main form of authentication. After authenticating, I create a user in my users collection with these details:
{
phoneNumber: FirebaseAuth.instance.currentUser().phoneNumber,
displayName: 'Comes from user textbox',
...
}
But say one day a user want's to change their phone number. How do I do this? Because I cannot simply change the user's phone number in the document, because the phone number needs to be authenticated. And after authentication, the user gets a new authUID. Which should then be a new user?
Could someone explain the logic behind a user that wants to keep their profile details but change their number.
In order to achieve this, you can use User.updatePhoneNumber. This allows you to update the phone number of a user.
You would use it in the same manner that you also authenticated with phone number in the first place (using signInWithCredential), i.e. you retrieve a credential using FirebaseAuth.verifyPhoneNumber and pass the credential that you get from either verificationCompleted or your user when they enter the SMS code they received. I will only sketch out what this would look like as I assume that you know how to perform this task:
FirebaseAuth.instance.verifyPhoneNumber(
phoneNumber: phoneNumber,
timeout: const Duration(minutes: 2),
verificationCompleted: (credential) async {
await (await FirebaseAuth.instance.currentUser()).updatePhoneNumber(credential);
// either this occurs or the user needs to manually enter the SMS code
},
verificationFailed: null,
codeSent: (verificationId, [forceResendingToken]) async {
String smsCode;
// get the SMS code from the user somehow (probably using a text field)
final AuthCredential credential =
PhoneAuthProvider.getCredential(verificationId: verificationId, smsCode: smsCode);
await (await FirebaseAuth.instance.currentUser()).updatePhoneNumber(credential);
},
codeAutoRetrievalTimeout: null);
When updatePhoneNumber is called, you probably also want to update your database document. Alternatively, you could listen to onAuthStateChanged and update your document this way.
async function save(phone: string, e) {
e.preventDefault();
const { currentUser:fuser } = firebase.auth();
if(fuser && fuser.phoneNumber !== phone) {
try {
const verifier = new firebase.auth.RecaptchaVerifier('recaptcha-container', {
callback: (response) => console.log('callback', response),
size: 'invisible',
});
const phoneProvider = new firebase.auth.PhoneAuthProvider();
const id = await phoneProvider.verifyPhoneNumber(phone, verifier);
const code = window.prompt('Bitte zugeschickten Code eingeben');
const cred = firebase.auth.PhoneAuthProvider.credential(id, code);
await fuser.updatePhoneNumber(cred);
console.log('phone number changed', id, cred, fuser);
setSuccess(true);
} catch(e) {
console.error(e);
}
}
}
I am trying to navigate to a specific page when a notification is clicked. The onResume and onMessage callbacks are invoked when I click on the notification and I can see the message in the log screen. However, when I try to navigate to a specific page, I am not able to do that and there is no error message in the log too. P.S. When I used a Navigator key to access the state of the context(since in initState, the navigator cannot be used) I got an error saying no context to build. What is the mistake ??
I have tried Navigator.push, Calling a method and routing from within that method, used navigator key.
void initState() {
messaging.configure(
onMessage: (Map<String, dynamic> message) async {
print('onMessage: $message');
Navigator.of(context).push(
MaterialPageRoute<BuildContext>(builder: (_) => PageContent(value:1)));
},
onLaunch: (Map<String, dynamic> message) async {
print('onLaunch: $message');
Navigator.of(context).push(
MaterialPageRoute<BuildContext>(builder: (_) => PageContent(value:2)));
},
onResume: (Map<String, dynamic> message) async {
print('onResume:- This is the message $message');
Navigator.of(context).push(
MaterialPageRoute<BuildContext>(builder: (_) => MoviesList()));
},
);
I expect the code to be loaded when the notification is tapped and route to a new page( MoviesList or PageContent in my case). But only my home screen is visible.
Context is not available in init state
I came across this issue and get resolved using redux concepts
add a key in a global state like appNavigator
sample code for global app state (app_state.dart),
import 'package:built_collection/built_collection.dart';
import 'package:built_value/built_value.dart';
import 'package:flutter/material.dart' hide Builder;
part 'app_state.g.dart';
abstract class AppState implements Built<AppState, AppStateBuilder> {
factory AppState([AppStateBuilder updates(AppStateBuilder builder)]) =
_$AppState;
AppState._();
static AppState initState() {
return new AppState((AppStateBuilder b) {
b
..appNavigator = new GlobalKey<NavigatorState>(debugLabel: 'debugLabel')
.. isLoggedIn = false
..isLoading = false;
});
}
// Never change this key through out the app lifecycle
GlobalKey<NavigatorState> get appNavigator;
// login state ***************************************************************************
bool get isLoggedIn;
// indicates loading state ***************************************************************************
bool get isLoading;
}
dispatch an action onMessage received from the notification like
onMessage: (Map<String, dynamic> message) async {
print('onMessage: $message');
store.dispatch(new RedirectUserOnNotification());
},
and in middleware route to a specific page with conditions validation as you needed.
void redirectuser(Store<AppState> store, RedirectUserOnNotification action,
NextDispatcher next) async {
store.state.appNavigator.currentState.pushNamed(someRouteName);
next(action);
}
Note: I have used build_value concepts in a model file