How to disable cloud messaging per device/user in flutter? - flutter

For a flutter app I’m using Firebase Cloud Messaging and cloud functions to send push notifications to users, using their FCM registration tokens. The app has a settings page where users should be able to turn off certain push notifications. The notifications are user specific, so a topic to subscribe or unsubscribe to wouldn’t work, but the notifications can be classified in certain categories.
For example in a chat app when user A send a message to user B that push notification could be in a category of ‘chat messages’, while user A could also delete the chat with user B and that push notification could be in a category of ‘deleted chats’.
How can I make it so that user B can turn off notifications for ‘deleted chats’, while still receiving notifications for ‘chat messages’? Is it possible to use a condition with a topic and a user’s registration token on one way or the other? Any ideas are greatly appreciated!

Thanks to a big nudge in the right direction from Doug, I was able to figure it out! Posting my code below to help anyone take the same step in the right direction.
So, in my flutter app' settings page the user can turn notifications on and off for a few categories. The user's preference is then stored in a user specific document in my Cloud Firestore users collection. See the below code for an example of the SwitchListTile I used on the settings page.
SwitchListTile(
title: Text('Admin notifications'),
subtitle: Text('Maintenance and general notes'),
onChanged: (value) {
setState(() {
adminNotifications = value;
Firestore.instance
.collection('users')
.document(loggedInUser.uid)
.updateData({
'adminNotifications': value,
});
});
save('adminNotifications', value);
},
value: adminNotifications,
),
In my cloud function I added a reference to the document in the users collection and a check to see if the value of the field adminNotifications is equal to true. If so, a notification is send, otherwise a notification is not send to the user. Below I've added the cloud function. Please do note that the cloud function renders 'nested promises' warnings, but it works for now! I'm still a Flutter beginner so I was pretty happy to get it working. Big thanks again to Doug!
exports.userNotifications = functions.firestore.document('notifications/{any}').onCreate((change, context) => {
const userFcm = change.data().fcmToken;
const title = change.data().title;
const body = change.data().body;
const forUser = change.data().for;
const notificationContent = {
notification: {
title: title,
body: body,
badge: '1',
click_action: 'FLUTTER_NOTIFICATION_CLICK',
}
};
var usersRef = db.collection('users');
var queryRef = usersRef.where('login', '==', forUser).limit(1).get()
.then(snapshot => {
snapshot.forEach(doc => {
const adminNotifications = doc.data().adminNotifications;
console.log(adminNotifications);
if(swapNotifications === true){
return admin.messaging().sendToDevice(userFcm, notificationContent)
.then(() => {
console.log('notification sent')
return
})
.catch(error =>{
console.log('error in sending notification', error)
})
} else {
console.log('message not send due to swapNotification preferences')
}
return console.log('reading user data success');
})
.catch(err => {
console.log('error in retrieving user data:', err)
})
});

Related

Firebase FCM background notifications click_action

i want to navigate to a specific screen route when i click on the background notification
for now the default behavior is just launching my app, so how and where do i change the default behavior of the click action
i'm sending the notification using cloud functions, here is the code
const payload: admin.messaging.MessagingPayload = {
notification: {
title: doc["senderName"],
body: doc["msg"],
sound: "default",
badge: "1",
},
data: {
type: "chat",
},
};
return fcm
.sendToDevice(tokens, payload);
somehow i want to access the type "chat" so from there i can navigate to a chat screen
Use onMessageOpenedApp to handle when a user presses a notification. RemoteMessage has data property which holds the custom parameters.
When you click on the notification from background state, onMessageOpened stream function is called.
Initialize this stream function in the initState of first page of the app and check the data in payload you received from the notification just like this:
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) async {
if (message != null) {
String screenName = message.data['screenName'];
print("Screen name is: $screenName");
if (screenName == 'chat') {
//Navigate to your chat screen here
}
}
});

React Query Optimistic Update causing flashing updates

I'm having an issue with React Query where if a user presses a button too fast, triggering a mutation, the correct value flashes and changes on the screen as the API calls are returned, even when attempting to cancel them. I notice this problem also happens in the official React Query example for optimistic updates. Here's a video I took of the problem happening there.
export const useIncreaseCount = () => {
const queryClient = useQueryClient()
return useMutation(
() => {
const cart = queryClient.getQueryData('cart') as Cart
return setCart(cart)
},
{
onMutate: async (cartItemId: string) => {
await queryClient.cancelQueries('cart')
const previousCart = queryClient.getQueryData('cart') as Cart
queryClient.setQueryData(
'cart',
increaseCount(previousCart, cartItemId)
)
return { previousCart }
},
onError: (error, _cartItem, context) => {
console.log('error mutating cart', error)
if (!context) return
queryClient.setQueryData('cart', context.previousCart)
},
onSuccess: () => {
queryClient.invalidateQueries('cart')
},
}
)
}
I'm thinking of debouncing the call to use useIncreaseCount, but then onMutate will get debounced, and I don't want that. Ideally just the API call would be debounced. Is there a built in way in React Query to do this?
The problem come from the fact that every onSuccess callback calls queryClient.invalidateQueries, even though a different invocation of the mutation is still running. It's the responsibility of the user code to not do that. I see two ways:
One thing we are doing sometimes is to track the amount of ongoing mutations with a ref (increment in onMutate, decrement in onSettled), then only call queryClient.invalidateQueries if the counter is zero.
assigning mutationKeys and using !queryClient.isMutating(key) should also work.

In flutter how to redirect to specific dashboard based on the type of user Sign In? [duplicate]

This question already has answers here:
How to sign in with different types of user in flutter and firebase?
(1 answer)
How to create 2 different User group in Firebase AUTH with Flutter
(1 answer)
How to create different user groups in Firebase?
(2 answers)
How can handle separate login for two types of users in same firebase app?
(1 answer)
Closed 1 year ago.
here I want to Navigate a user to specific dashboard after Login, How I can implement logic for this??
I have a requirement in my application to include two types of users. one type of user (tutor) will have access to a different dashboard after logging in and the second type (student) of user will have access to a different dashboard ? The login screen is the same for both the users. Based on the login credentials, In real time database?
if (formKey.currentState!.validate()) {
var password = passwordController.text;
var email = emailController.text;
ProgressDialog progressDialog = ProgressDialog(
context,
dialogTransitionType: DialogTransitionType.Bubble,
title: Text('Signing In'),
message: Text('Please wait'),
);
progressDialog.show();
FirebaseAuth auth = FirebaseAuth.instance;
try {
UserCredential userCredential =
await auth.signInWithEmailAndPassword(
email: email,
password: password,
);
//progressDialog.dismiss();
User? user = userCredential.user;
if (user != null) {
Here i want redirect to specific dashboard based on the type of user Sign In?
}
} on FirebaseAuthException catch (e) {
progressDialog.dismiss();
if (e.code == 'user-not-found') {
Fluttertoast.showToast(
msg: 'User not found', backgroundColor: Colors.red);
} else if (e.code == 'wrong-password') {
Fluttertoast.showToast(
msg: 'Wrong password', backgroundColor: Colors.red);
}
}
}
},
},
You are using firebase authentication with email and password.
It manages users with the same Model that is User. So it is impossible for you to handle this redirection.
But the most easiest approach is to save user's data to firestore after authentication (you save email, name, isTeacher,....). [NB: The ID of the firestore document should be the uniqueid of the user after authentication]
After the user has been authenticated, you call a method getUser() that will return wheter your custom userModel.
Finally, if the isTeacher is true navigate to Teacher dashboard else navigate to user homescreen

Firebase Cloud Messaging onLaunch callback

My app structure is a little bit mess, but I have to add this patch first and then I'll restructure the entire logic. The thing is I first check if there's a firebase user, then if there is one I use StreamBuilder to get the current user profile from Firestore, then I have the _firebaseMessaging.configure method because onLaunch and onResume I use this callback:
void _navigateToGestorResevas(Map<String, dynamic> message, User currentUser) {
Navigator.push(context,
MaterialPageRoute(builder: (context) =>
GestorScreen(user: currentUser)));
}
Because I need to send the User to this screen where he fetch the message from firebase.
onResume this works fine, but onLaunch it goes to the screen and fetch the data but there are like 20 seconds where there are some kind of glitch. It switch like 20-30 times between two states where I have and no have snapshot data in this _initState func:
final snapshot = await _dbRef.child('mensajes').child(widget.user.id).once();
if (snapshot.value != null) {
setState(() {
hayMensajes = true;
});
final data = snapshot.value;
for (var entry in data.entries) {
Message message = Message.fromJson(entry.value);
setState(() {
message.add(message);
});
}
} else {
setState(() {
hayMensajes = false;
});
}
Anyone have an idea what am I doing wrong?
If I am not mistaken, there are some active issues about FCM onLaunch callback with flutter. Some of them are still not fixed. One of the problems most people had to face was that onLaunch callback being called multiple times. I don't know why it happened, but as in your case, you can possibly get rid of the issue by some temporary fixes.
If the same screen is getting pushed over and over again, and glitching, you can pop the stack until it reaches the one you meant to open and set a condition to push navigator only if the new route is different from the old one. Using the named routes,
Navigator.popUntil(context, ModalRoute.withName(routeName));
if (ModalRoute.of(context).settings.name != routeName) {
Navigator.pushNamed(context, routeName);
}
I am not sure if that was the problem you asked, but I hope at least my answer helps somehow.

Flutter : Sending Email

i want little ask about sending email with flutter . I using https://pub.dev/packages/flutter_email_sender#-readme-tab- for sending email.
Sending Function
Future sendEmail(String subject,String body,List<String> recipients) async{
final Email email = Email(body: body,subject: subject,recipients: recipients);
String platformResponse;
try {
await FlutterEmailSender.send(email);
platformResponse='success';
} catch (e) {
platformResponse = e.toString();
}
print(platformResponse);
}
View.dart
Center(
child: RaisedButton(
onPressed: () => _sendMail(),
child: Text('send'),
),
)
void _sendMail() async {
return await api.sendEmail(widget.namaUpdate, widget.jurusanUpdate,['zefry.reynando#gmail.com']);
}
it's possible sending email automatic without open gmail app first ? (Like in codeigniter)
i trying using another package but always open gmail app first. or this how it works?
Thanks
You're not likely to find a package that sends email out without either configuration or a visible mail client. That app would not by approved by either Apple or Google, because it might be a source of SPAM.