i am currently using firebase as database and to notify the user whenever any changes occur on database i am using fcm . i have created firebase function through node.js and whenever any change occurs on database it'triggered and notification will be send to the user but instead of showing notification once its showing three times the same notification. i search a lot on internet but have not found the solution yet.
Here is the screenShot https://imgur.com/a/zNXjo
Any help will be appreciated.
This is my FirebaseMessagingClass
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
/**
* Created by HP on 04-07-2017.
*/
public class MyFirebaseMessagingService extends FirebaseMessagingService {
private static final String TAG = "FirebaseMessagingServce";
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
String notificationTitle = null, notificationBody = null;
super.onMessageReceived(remoteMessage);
if (remoteMessage.getNotification() != null) {
Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody());
notificationTitle = remoteMessage.getNotification().getTitle();
notificationBody = remoteMessage.getNotification().getBody();
}
// Also if you intend on generating your own notifications as a result of a received FCM
// message, here is where that should be initiated. See sendNotification method below.
sendNotification(notificationTitle, notificationBody);
}
private void sendNotification(String notificationTitle, String notificationBody) {
Intent intent = new Intent(this, FragmentTwo.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent,
PendingIntent.FLAG_ONE_SHOT);
Uri defaultSoundUri= RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this);
// .setAutoCancel(true)
// .setSmallIcon(R.mipmap.ic_launcher)
// .setContentIntent(pendingIntent)
// .setSound(defaultSoundUri);
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
{
notificationBuilder.setSmallIcon(R.mipmap.ic_launcher) //Notification icon
.setLargeIcon(BitmapFactory.decodeResource(this.getResources(),
R.mipmap.ic_launcher))
.setAutoCancel(true) //Automatically delete the notification
.setSound(defaultSoundUri)
.setContentTitle(notificationTitle)
.setContentText(notificationBody)
.setContentIntent(pendingIntent);
} else
{
notificationBuilder.setSmallIcon(R.mipmap.ic_launcher)
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setContentTitle(notificationTitle)
.setContentText(notificationBody)
.setContentIntent(pendingIntent);
}
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(0, notificationBuilder.build());
}
}
Ans this is my service class
import android.util.Log;
import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.iid.FirebaseInstanceIdService;
/**
* Created by HP on 07-09-2017.
*/
public class myInstanceService extends FirebaseInstanceIdService {
private static final String TAG=FirebaseInstanceIdService.class.getSimpleName();
#Override
public void onTokenRefresh() {
String refreshedToken = FirebaseInstanceId.getInstance().getToken();
Log.d(TAG, "Refreshed token: " + refreshedToken);
// If you want to send messages to this application instance or
// manage this apps subscriptions on the server side, send the
// Instance ID token to your app server.
sendRegistrationToServer(refreshedToken);
}
private void sendRegistrationToServer(String refreshedToken) {
}
}
Tis is my cloud function code
const functions = require('firebase-functions');
// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
// exports.helloWorld = functions.https.onRequest((request, response) => {
// response.send("Hello from Firebase!");
// });
// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
// Listens for new messages added to messages/:pushId
exports.pushNotification = functions.database.ref('/News/{pushId}').onWrite( event => {
console.log('Push notification event triggered');
// Grab the current value of what was written to the Realtime Database.
var valueObject = event.data.val();
if(valueObject.Image != null) {
valueObject.Image= "Sent you a photo!";
}
// Create a notification
const payload = {
notification: {
title:valueObject.Title,
body: valueObject.Description || valueObject.Image,
sound: "default"
},
};
//Create an options object that contains the time to live for the notification and the priority
const options = {
priority: "high",
timeToLive: 60 * 60 * 24
};
return admin.messaging().sendToTopic("pushNotifications", payload, options);
});
Related
enter code hereI have case i needs 2 connector MongoClient. first client to connect to local mongodb server. 2nd client to connect to remote mongodb. 2 client inside 1 file database-connector.js. is it right or wrong in this ways? what the correct ways in this problem?
class CatchError {
static error: string | unknown;
static save(error: string | unknown) {
this.error = error;
const re = new RegExp('ECONNREFUSED', 'i')
if (re.test(error.message)) {
// fs.write('system.error.log')
}
}
}
class DBClient {
client: MongoClient;
constructor(url: string, config: Partial<MongoClientOptions>) {
this.client = new MongoClient(url, config)
}
async connect() {
try {
this.client = await this.client.connect();
return this.client;
} catch(error) {
CatchError.parse(error)
}
}
}
// for entire data application
export const localDBClient = initializeDBConnection("mongodb://localhost");
// only for authentication to access application
export const remoteDBClient = initializeDBConnection("mongodb+srv://se132.aws-slcuster.com");
gui.js
import { remoteDBClient } from "database-connector.js";
// use client //
model.js
import { localDBClient } from "database-connector.js";
// use client //
I am attempting to make an application that allows a registered user to submit an image file (.jpeg, .png, etc) along with a title of that image. I am having trouble wrapping my mind around how to do this.
I need to send the image to an amazon AWS S3 bucket, and I was able to figure out how to do this, but adding a title input has confused me a lot in terms of how to get both the file and title from my front-end (JSON) to my back-end API and have it be saved to my post database (JPA). I have a user_post database that has the following column: ID (post ID primary key), user_id (foreign key from User table), title, & post_image (containing the file that is to be saved in the DB). So basically, I need to figure out how to get the input (both file and title) at once, send it to my back-end and save it in the Post DB.
Do I need to pass the title as an argument in my post controller? Since the UserPost DB is a one to many relationship with a User, I need to pass that logged in user's ID to my controller so that it gets sent to the post DB, I'm just not sure how.
I have been searching online everywhere for help and figured I would give Stack Overflow a shot on this as I desperately need help. I am using JWT authentication through local storage for now to access user information such as username and id. Before I was testing my API with Postman by using the "file" key in the body and then proceeding to choose the image that is to be selected, but how can I do this if I want to add a title as well? Ideally, it would be the same but with text instead of file for the type as seen below but I am not sure. (Of course depending on what key I set for the title if I am thinking of it right).
This is a screenshot of my front-end just for a visual in case it helps at all.
Here is my code, I apologize for it being so messy, I am working on cleaning it up as soon as I can. I will start with the front-end. I believe I need to set the key for my title input so that it gets passed to my back-end API, but I am not even sure if I am doing that right. I also needed to use a custom form validation since I am dealing with an image upload. I ended up putting the call to my API in my useForm.js file since my form validations are done in that custom hook.
Upload.jsx:
import '../../components/pages/styles/Uploads.css';
import {Formik, Field, Form, ErrorMessage} from 'formik';
import * as Yup from 'yup';
import {useEffect, useState} from 'react';
import {} from 'react-router-dom';
import {useDispatch, useSelector} from 'react-redux';
import axios from 'axios';
import {clearMessage} from '../../slices/messages';
import authHeader from '../../services/auth-header';
import useForm from '../../hooks/useForm';
const API_URL = 'http://localhost:8080/api/posts';
function Uploads(onUpload) {
const {user: currentUser} = useSelector((state) => state.auth);
const [file, setFile] = useState();
const [title, setTitle] = useState();
const [description, setDescription] = useState('');
const [loading, setLoading] = useState(false);
const [content, setContent] = useState('');
const [preview, setPreview] = useState(null);
const initialValues = {
title: '',
};
const [formErrors, setFormErrors] = useState();
useEffect(() => {}, []);
const handlePost = (formValue) => {};
const onAddImage = (file) => {
window.URL.revokeObjectURL(preview);
if (!file) return;
setPreview(window.URL.createObjectURL(file));
};
//The postUserImage function below was moved to useForm.js
//since my form validations are done there. I might have
//messed this up.
// const postUserImage = async (event) => {
//This is the code that will get passed to my backend. I need the image and title to be added here somehow.
// event.preventDefault();
// const formData = new FormData();
// formData.append('file', file);
// formData.append('title', title);
// const result = await axios.post('/upload', formData, {
// headers: {...authHeader(), 'Content-Type': 'multipart/form-data'},
// });
// console.log(result.data);
// };
//Custom hook call
// const {handleChange, values, errors, handleSubmit} = useForm(postUserImage);
const initialState = {title: ''};
const validations = [
({title}) => isRequired(title) || {title: 'Title is required'},
];
const {values, isValid, errors, changeHandler, submitHandler, touched} =
useForm(initialState, validations, onUpload);
return (
<div className='page'>
<div className='upload-card'>
<div id='preview'>
<img
src={preview || require('../../assets/user-solid.jpeg')}
id='image'
alt='Thumbnail'
className='user-post'
/>
</div>
</div>
<div className='upload-container'>
<div className='post-form-container'>
<p id='upload-form-label'>Hello, feel free to post an image!</p>
<form
// onSubmit={'return Validate(this);'}
onSubmit={submitHandler}
className='upload-form'
>
<div className='panel'>
<div className='button_outer'>
<div className='btn_upload'>
<input
filename={file}
onChange={(e) => onAddImage(e.target.files[0])}
type='file'
accept='.jpeg,.svg,.gif,.png'
id='image-selection-btn'
></input>
Choose your Art
</div>
</div>
</div>
<input
name='title'
type='text'
className='form-control'
placeholder='Enter Title'
id='cred-input'
required
value={values.title}
onChange={changeHandler}
/>
{touched.title && errors.title && (
<p className='error'>{errors.title}</p>
)}
<button type='submit' id='post-upload-btn' disabled={isValid}>
Upload Image
</button>
</form>
</div>
</div>
</div>
);
function isRequired(value) {
return value != null && value.trim().length > 0;
}
function isSame(value1, value2) {
return value1 === value2;
}
}
export default Uploads;
useForm.js:
import React, {useState} from 'react';
import {omit} from 'lodash';
import authHeader from '../services/auth-header';
import axios from 'axios';
function useForm(initialState = {}, validations = [], onSubmit = () => {}) {
const API_URL = 'http://localhost:8080/api/posts';
// Add the 'onSubmit' argument
const {isValid: initialIsValid, errors: initialErrors} = validate(
validations,
initialState
);
const [values, setValues] = useState(initialState);
const [errors, setErrors] = useState(initialErrors);
const [isValid, setValid] = useState(initialIsValid);
const [touched, setTouched] = useState({});
const [file, setFile] = useState();
const [title, setTitle] = useState();
const changeHandler = (event) => {
const newValues = {...values, [event.target.name]: event.target.value};
const {isValid, errors} = validate(validations, newValues);
setValues(newValues);
setValid(isValid);
setErrors(errors);
setTouched({...touched, [event.target.name]: true});
};
// Add this
const submitHandler = async (event) => {
event.preventDefault();
onSubmit(values);
const formData = new FormData();
formData.append('file', file);
formData.append('title', values.title);
const result = await axios.post('/upload', formData, {
headers: {...authHeader(), 'Content-Type': 'multipart/form-data'},
});
console.log(result.data);
};
return {values, changeHandler, isValid, errors, touched, submitHandler}; // Add 'submitHandler'
}
function validate(validations, values) {
const errors = validations
.map((validation) => validation(values))
.filter((validation) => typeof validation === 'object');
return {
isValid: errors.length === 0,
errors: errors.reduce((errors, error) => ({...errors, ...error}), {}),
};
}
export default useForm;
Back-end code:
User.java:
package com.Application.models;
import javax.persistence.*;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
#Entity
#Table( name = "users",
uniqueConstraints = {
#UniqueConstraint(columnNames = "username"),
})
public class User {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "username")
#NotBlank
#Size(max = 20)
private String username;
#Column(name = "email")
#NotBlank
#Size(max = 50)
#Email
private String email;
#NotBlank
#Size(max = 120)
private String password;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable( name = "user_roles",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<>();
#OneToMany(cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
mappedBy = "user")
#Column(name = "user_post")
private Set<UserPosts> userPosts = new HashSet<>();
public User(String username, String email
,String password) {
this.username = username;
this.email = email;
this.password = password;
}
public User() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
}
UserRepository.java:
package com.Application.repository;
import java.util.Optional;
import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.HashTek.HashTekApplication.models.User;
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
Optional<User> findByEmail(String email);
Boolean existsByUsername(String username);
Boolean existsByEmail(String email);
}
UserPost.java:
package com.Application.models;
import javax.persistence.*;
#Entity
#Table(name = "user_posts")
public class UserPosts {
#Id
#Column(name = "id")
private Long id;
#ManyToOne(cascade = {CascadeType.ALL}, fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", nullable = false)
private User user;
#Column(name = "title")
private String title;
#Column(name = "post_image")
private String postImage;
#Column(name = "likes")
private Long likes;
#Column(name = "views")
private Long views;
public void setId(Long id) {
this.id = id;
}
public Long getId() {
return id;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getPostImage() {
return postImage;
}
public String getPostImageComplete() {
return " https://hashtekbucket.s3.us-east-2.amazonaws.com/" + postImage;
}
public void setPostImage(String postImage) {
this.postImage = postImage;
}
public Long getLikes() {
return likes;
}
public void setLikes(Long likes) {
this.likes = likes;
}
public Long getViews() {
return views;
}
public void setViews(Long views) {
this.views = views;
}
}
UserPostController.java:
package com.Application.controller;
import com.Application.models.User;
import com.Application.models.UserPosts;
import com.Application.payload.response.MessageResponse;
import com.Application.repository.UserPostsRepository;
import com.Application.security.jwt.JwtUtils;
import com.Application.security.services.CustomUserDetails;
import com.Application.security.services.CustomUserDetailsService;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.PutObjectRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import static org.apache.http.entity.ContentType.*;
import static org.apache.http.entity.ContentType.IMAGE_GIF;
#CrossOrigin(origins = "http://localhost:3000")
#RestController
#RequestMapping("/api/posts")
public class UserPostsController {
private static final String AUTH_HEADER = "authorization";
// #Autowired
// private final UserPostsRepository userPostRepo;
#Autowired
private CustomUserDetailsService userDetailsService;
#Autowired
JwtUtils jwtUtils;
/** AWS CREDENTIALS SOURCE
For AWS Builder **/
#Value("${cloud.aws.credentials.access-key}")
private String accessKey;
#Value("${cloud.aws.credentials.secret-key}")
private String accessSecret;
#Value("${cloud.aws.region.static}")
private String region;
// public UserPostsController(UserPostsRepository userPostRepo) {
// this.userPostRepo = userPostRepo;
// }
#PostMapping(
path = "/upload",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity < ? > userPostUpload(#RequestParam("file") MultipartFile file,
RedirectAttributes redirectAttributes,
Model model,
UserPosts userPosts, HttpServletRequest request, User user) {
//Check if POST is empty
if (file.isEmpty()) {
return ResponseEntity.ok(new MessageResponse("Please select a file to upload"));
}
isImage(file);
//Hard coded bucketName -> linked to AWS
String bucketName = "hashtekbucket";
//Add timestamp to name to prevent duplicate names
String fileName = System.currentTimeMillis() + "_" + file.getOriginalFilename();
//getting aws access
AWSCredentials credentials = new BasicAWSCredentials(accessKey, accessSecret);
//Building S3Client connection
AmazonS3 s3Client = AmazonS3ClientBuilder.standard()
.withRegion(Regions.US_EAST_2)
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.withRegion(region).build();
try {
// //PUSH TO BUCKET
File fileObj = convertMultiPartFileToFile(file);
s3Client.putObject(new PutObjectRequest(bucketName, fileName, fileObj));
fileObj.delete();
//Show Successful Upload
redirectAttributes.addFlashAttribute("message_2", fileName + ": SuccessFully Uploaded On AWS S3");
//JWT Token retrieval from HTTP request header
final String authHeader = request.getHeader(AUTH_HEADER);
String username = null;
String jwt = null;
if (authHeader != null && authHeader.startsWith("Bearer")) {
jwt = authHeader.substring(7);
username = jwtUtils.getUserNameFromJwtToken(jwt);
}
//Load logged in Username from JWT Token obtained
CustomUserDetails customUser = (CustomUserDetails) userDetailsService.loadUserByUsername(username);
Long userId = customUser.getId();
UserPosts myUserPost = new UserPosts();
myUserPost.setId(userId);
myUserPost.setPostImage(fileName);
// The code under here is how I saved a user profile by ID, // but I am getting an error in my post repo
// since there is no post Id until a user creates one.
// //Find User by id
// userProfile = userProfRepo.findByUserId(userId);
//
// //Set resource name to
// userProfile.setProfile_banner(fileName);
//
// //Save database changes
// userProfRepo.save(userProfile);
} catch (Exception e) {
e.printStackTrace();
}
model.addAttribute("userPosts", userPosts);
return ResponseEntity.ok(new MessageResponse("File Upload Successful"));
}
private void isImage(MultipartFile file) {
if (!Arrays.asList(
IMAGE_JPEG.getMimeType(),
IMAGE_SVG.getMimeType(),
IMAGE_PNG.getMimeType(),
IMAGE_GIF.getMimeType()).contains(file.getContentType())) {
throw new IllegalStateException("File must be an image [" + file.getContentType() + "]");
}
}
/**
File Conversion
**/
private File convertMultiPartFileToFile(MultipartFile file) {
File convertedFile = new File(file.getOriginalFilename());
try (FileOutputStream fos = new FileOutputStream(convertedFile)) {
fos.write(file.getBytes());
} catch (IOException e) {
// log.error("Error converting multipartFile to file", e);
}
return convertedFile;
}
}
If anyone could help me at all, any information, tips, or anything, it would mean the world. If I am missing anything that seems important please let me know.
So basically, I need to figure out how to get the input (both file AND
title) at once,
You need to send a POST request to the backend with your image and the title. It HTML 5, the standard way to do this is with a form with an input of type file
There is actually also JavaScript API to set the file input to only accept image files; I think this worked using file extension.
send it to my backend and save it in the Post DB. Am I
gonna need to pass the title as an argument in my post controller?
Standard file posts have the name of the file included
Since the UserPost DB is a one to many relationship with a User, I am
gonna need to pass that logged in user's ID to my controller so that
it gets sent to the post DB, just not sure how.
You should not send the user id. In fact, you should typically be wary of sending the user id to the front end at all. Everything in javascript can be inspected by the user... or someone else who sits at his/her desk.
Instead you can rely on the current session (create on the backend) for user identification.
I want to show my notifications in a channel that I create so I can fully customize my channel with my preferences. I'm using a Firebase function to send notifications (messages) from user to user:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.pushNotification = functions.firestore.document('/devices/{tokenId}/notifications/{notificationId}')
.onWrite((change, context) => {
console.log('Push notification event triggered');
const tokenId = context.params.tokenId;
const document = change.after.exists ? change.after.data() : null;
if (document == null) {
return console.log('A notification has been deleted from database');
}
const payload = {
notification: {
title: document.username,
body: document.message,
sound: "default"
},
data: {
sender: document.sender
}
};
const options = {
priority: "high",
timeToLive: 60 * 60 * 24 //24 hours
};
return admin.messaging().sendToDevice(tokenId, payload, options).then(result => {
console.log('A notification sent to device with tokenId: ', tokenId);
});
});
I have implemented my FirebaseMessagingService service:
#Override
public void onMessageReceived(#NonNull RemoteMessage remoteMessage) {
showNotification(remoteMessage);
}
private void showNotification(RemoteMessage remoteMessage) {
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
RemoteMessage.Notification remoteNotification = remoteMessage.getNotification();
if (remoteNotification == null) return;
String title = remoteNotification.getTitle();
String message = remoteNotification.getBody();
Notification notification;
Notification.Builder builder = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O ?
new Notification.Builder(this, CH_MESSAGE) : new Notification.Builder(this);
notification = builder
.setContentTitle(title)
.setContentText(message)
.setCategory(CATEGORY_MESSAGE)
.build();
notificationManager.notify(0, notification);
}
And created my own notification channel at my Application class:
#Override
public void onCreate() {
super.onCreate();
createNotificationChannels();
}
private void createNotificationChannels() {
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if (notificationManager == null) return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel notificationChannel = new NotificationChannel(CH_MESSAGE,
getString(R.string.messages), NotificationManager.IMPORTANCE_HIGH);
notificationChannel.setDescription(getString(R.string.message_channel_description));
notificationManager.createNotificationChannel(notificationChannel);
}
}
I can successfully send notifications, but notifications are going to Miscellaneous channel.
I tried to remove the channel by using its channel ID with notificationManager.deleteNotificationChannel("fcm_fallback_notification_channel");, but it still recreates the channel and sends the notification there. How can I remove Miscellaneous channel permanently and handle my notifications with my own channels?
I have found the problem I am facing, my message payload contains both notification and data fields. According to this documentation my messages were not calling onMessageReceived method when app is in background. Now I'm using only data payload which calls the method when app is at both background and foreground.
const payload = {
data: {
sender: document.sender,
username: document.username,
message: document.message
}
};
And in my onMessageReceived method:
Map<String, String> remoteMap = remoteMessage.getData();
String senderUid = remoteMap.get("sender");
String senderUsername = remoteMap.get("username");
String message = remoteMap.get("message");
Notification.Builder builder = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O ?
new Notification.Builder(this, CH_MESSAGE) : new Notification.Builder(this);
Notification notification = builder
.setContentTitle(senderUsername)
.setContentText(message)
.setSmallIcon(R.drawable.ic_notif_message)
.build();
notificationManager.notify(0, notification);
I'm using #azure/service-bus JavaScript library to publish and subscribe to messages on Azure Service Bus topic from Azure Functions.
To receive messages, I'm using Azure Service Bus Topic trigger function created from the template without any changes.
When I publish message using sender.send(message) I receive it fine.
import { AzureFunction, Context, HttpRequest } from "#azure/functions"
import * as sb from "#azure/service-bus"
const PublishToServiceBus: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
const eventDoc = req.body;
const serviceBusConnectionString = process.env["ServiceBusConnection"];
const topicName = process.env["TopicName"]';
const sbClient = sb.ServiceBusClient.createFromConnectionString(serviceBusConnectionString);
const topicClient = sbClient.createTopicClient(topicName);
const sender = topicClient.createSender();
const message: sb.SendableMessageInfo = { body: eventDoc };
// this works
sender.send(message);
// this creates message without body?
const scheduledEnqueueTimeUtc = new Date(Date.now() + 10000);
sender.scheduleMessages(scheduledEnqueueTimeUtc, [message]);
};
export default PublishToServiceBus;
But when I schedule message with sender.scheduleMessages(), my incoming binding variable is undefined in Azure Service Bus Topic trigger function.
import { AzureFunction, Context } from "#azure/functions"
const serviceBusTopicTrigger: AzureFunction = async function (context: Context, mySbMsg: any): Promise<void> {
context.log('ServiceBus topic trigger function processed message', mySbMsg);
};
export default serviceBusTopicTrigger;
Output: ServiceBus topic trigger function processed message undefined
Is this a problem with the library or I'm doing something wrong?
The issue is caused by a bug in the #azure/service-bus sdk.
Workaround
Import DefaultDataTransformer from "#azure/amqp-common" library.
In typescript, import { DefaultDataTransformer } from "#azure/amqp-common";
In javascript, const { DefaultDataTransformer } = require("#azure/amqp-common");
Update the message body before calling the scheduleMessage() method to send the message as follows
Instantiate the data transformer used by the sdk:
const dt = new DefaultDataTransformer();
When you need to schedule the message, encode the message body before sending:
message.body = dt.encode(message.body);
More Reference and Investigation around this bug - Azure/azure-sdk-for-js#6816
in the new world (azure functions .net5) you can no longer use brokered messages. the new libraries do not cater for it.
Function app declaration is no longer [FunctionName=] but [Function=
You can no longer receive 'Message' or byte. but only a string!!!.
example;
[Function("TestFA")]
public async Task Run([ServiceBusTrigger(topicName, subscriberName, Connection = ???)] string messageText, string id, FunctionContext executionContext)
the magic is now in FunctionContext executionContext
you can get properties from this
e.g KeyValuePair<string, object> props = executionContext.BindingContext.BindingData.Where(x => x.Key == "UserProperties").FirstOrDefault();
Not sure if I am doing something wrong but using this api https://www.firebase.com/docs/security/simple-login-email-password.html I can successfully create a user - according to the return message, but I can not see that user anywhere in the Forge console. How do you know what users are registered?
Should I be taking the return user ID and creating my own user object in Firebase or is this duplication unnecessary. I do need to add some additional user properties so perhapes I will need to do this anyway.
When using email / password authentication in Firebase Authentication (previously known as Firebase SimpleLogin), your user's email and password combination is securely stored separately from the data actually stored in your Firebase.
This barrier between the data in your Firebase and your users' email / password hash combinations is by design: we want to make it easier for you to (1) develop your application, (2) prevent any accidental user credential leaks, and (3) still give you total flexibility with how to store your user data in Firebase.
That means that we only store the email address / password hash combination and nothing else, so it is up to you to decide how to store actual user data in your Firebase. As you suggested, you should be taking the user id and storing that data in your Firebase in a location such as /users/$id, and using the Firebase Security Rules Language to determine read / write access to that data. Your user's unique id and email are already in the auth variable you'll use when writing rules.
Here i created an Android program to do what Rob said for firebase beginner(like me)
out there.this program first store the username of the signedUp or signedIn user and then display them in a listView
SignInActivity.java
public class SignInActivity extends BaseActivity implements View.OnClickListener,View.OnKeyListener{
private DatabaseReference mDatabase;
public static FirebaseAuth mAuth;
private static final String TAG = "MainActivity";
EditText usernameField;
EditText passwordField;
TextView changeSignUpModeTextView;
Button signUpButton;
ImageView logo;
RelativeLayout relativeLayout;
Boolean signUpModeActive;
static ArrayList<String> userList = new ArrayList<>();
#Override
public void onStart() {
super.onStart();
// Check auth on Activity start
if (mAuth.getCurrentUser() != null) {
onAuthSuccess(mAuth.getCurrentUser());
}
}
#Override
public boolean onKey(View view, int i, KeyEvent keyEvent) {
if(i == keyEvent.KEYCODE_ENTER && keyEvent.getAction() == keyEvent.ACTION_DOWN){
signUpOrLogIn(view);
}
return false;
}
#Override
public void onClick(View view) {
if(view.getId() == R.id.changeSignUpMode){
if (signUpModeActive == true){
signUpModeActive = false;
changeSignUpModeTextView.setText("Sign Up");
signUpButton.setText("Log In");
}else{
signUpModeActive = true;
changeSignUpModeTextView.setText("Log In");
signUpButton.setText("Sign Up");
}
}else if(view.getId() == R.id.logo || view.getId() == R.id.relativeLayout){
InputMethodManager inm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
inm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(),0);
}
}
public void signUpOrLogIn(View view) {
showProgressDialog();
String email = usernameField.getText().toString().trim();
String password = passwordField.getText().toString().trim();
if (signUpModeActive == true) {
mAuth.createUserWithEmailAndPassword(email,password)
.addOnCompleteListener(MainActivity.this, new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
hideProgressDialog();
Toast.makeText(MainActivity.this, "createUserWithEmail:onComplete:" + task.isSuccessful(), Toast.LENGTH_SHORT).show();
// If sign in fails, display a message to the user. If sign in succeeds
// the auth state listener will be notified and logic to handle the
// signed in user can be handled in the listener.
if (!task.isSuccessful()) {
Toast.makeText(MainActivity.this, "Authentication failed." + task.getException().toString().substring(task.getException().toString().indexOf(" ")),
Toast.LENGTH_SHORT).show();
Log.i("Error", task.getException().toString());
} else {
onAuthSuccess(task.getResult().getUser());
showUserList();
}
}
});
} else {
mAuth.signInWithEmailAndPassword(email,password)
.addOnCompleteListener(MainActivity.this, new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
hideProgressDialog();
// If sign in fails, display a message to the user. If sign in succeeds
// the auth state listener will be notified and logic to handle the
// signed in user can be handled in the listener.
if (!task.isSuccessful()) {
// there was an error
Toast.makeText(MainActivity.this, task.getException().toString(),
Toast.LENGTH_LONG).show();
} else
{
onAuthSuccess(task.getResult().getUser());
showUserList();
}
}
});
}
}
public void showUserList(){
startActivity(new Intent(getApplicationContext(), UserList.class));
finish();
}
private void onAuthSuccess(FirebaseUser user) {
String username = usernameFromEmail(user.getEmail());
// Write new user
writeNewUser(user.getUid(), username, user.getEmail());
// Go to MainActivity
}
private String usernameFromEmail(String email) {
if (email.contains("#")) {
return email.split("#")[0];
} else {
return email;
}
}
private void writeNewUser(String userId, String name, String email) {
User user = new User(name, email);
mDatabase.child("users").child(userId).setValue(user);
ArrayList<String> userNames = new ArrayList<>();
userNames.add(name);
mDatabase.child("usernamelist").setValue(userNames);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mAuth = FirebaseAuth.getInstance();
mDatabase = FirebaseDatabase.getInstance().getReference();
if(mAuth.getCurrentUser()!=null){
showUserList();
}
usernameField = (EditText) findViewById(R.id.username);
passwordField = (EditText) findViewById(R.id.password);
changeSignUpModeTextView = (TextView) findViewById(R.id.changeSignUpMode);
signUpButton = (Button) findViewById(R.id.signupbutton);
logo = (ImageView)findViewById(R.id.logo);
relativeLayout= (RelativeLayout)findViewById(R.id.relativeLayout);
signUpModeActive = true;
changeSignUpModeTextView.setOnClickListener(this);
usernameField.setOnKeyListener(this);
passwordField.setOnKeyListener(this);
logo.setOnClickListener(this);
relativeLayout.setOnClickListener(this);
}
}
UserList.java
public class UserList extends AppCompatActivity {
private static final String TAG = "UserList" ;
private DatabaseReference userlistReference;
private ValueEventListener mUserListListener;
ArrayList<String> usernamelist = new ArrayList<>();
ArrayAdapter arrayAdapter;;
ListView userListView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user_list);
userlistReference = FirebaseDatabase.getInstance().getReference().child("usernamelist");
onStart();
userListView = (ListView) findViewById(R.id.userlistview);
}
#Override
protected void onStart() {
super.onStart();
final ValueEventListener userListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
usernamelist = new ArrayList<>((ArrayList) dataSnapshot.getValue());
usernamelist.remove(usernameOfCurrentUser());
Log.i(TAG, "onDataChange: "+usernamelist.toString());
arrayAdapter = new ArrayAdapter(UserList.this,android.R.layout.simple_list_item_1,usernamelist);
userListView.setAdapter(arrayAdapter);
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.w(TAG, "onCancelled: ",databaseError.toException());
Toast.makeText(UserList.this, "Failed to load User list.",
Toast.LENGTH_SHORT).show();
}
};
userlistReference.addValueEventListener(userListener);
mUserListListener = userListener;
}
public String usernameOfCurrentUser()
{
String email = MainActivity.mAuth.getCurrentUser().getEmail();
if (email.contains("#")) {
return email.split("#")[0];
} else {
return email;
}
}
#Override
public void onStop() {
super.onStop();
// Remove post value event listener
if (mUserListListener != null) {
userlistReference.removeEventListener(mUserListListener);
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) {
case R.id.action_logout:
FirebaseAuth.getInstance().signOut();
startActivity(new Intent(this, MainActivity.class));
finish();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}
You can use Google Identity Toolkit API to get a list of all registered users in your Firebase project, this API is used by the Firebase CLI which can be accessed by running firebase auth:export results-file
Make sure Identity Toolkit API is enabled
firebase-users-list.js
const serviceAccount = require('path/to/firebase-sdk-json-service-account');
const googleapis = require('googleapis');
const identitytoolkit = googleapis.identitytoolkit('v3');
const authClient = new googleapis.auth.JWT(
serviceAccount.client_email,
null,
serviceAccount.private_key,
['https://www.googleapis.com/auth/firebase'],
null
);
authClient.authorize((err) => {
if (err) {
return console.error(err);
}
let nextPageToken = undefined;
let users = [];
const getAccounts = () => identitytoolkit.relyingparty.downloadAccount({
auth: authClient,
resource: {
targetProjectId: serviceAccount.project_id,
maxResults: 200,
nextPageToken: nextPageToken
}
}, (e, results) => {
if (e) {
return console.error(err);
}
users = users.concat(results.users);
if (results.nextPageToken) {
nextPageToken = results.nextPageToken;
return getAccounts();
} else {
console.log(users);
}
});
getAccounts();
});
It's possible to use cloud function to fetch users list (view docs at firebase). Note, in the following example custom claims feature is used to check if user has enough privileges.
// USERS: return full users list for admin
// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
import * as admin from 'firebase-admin'
import * as functions from 'firebase-functions'
export const listUsers = functions.https.onCall((data, context) => {
// check if user is admin (true "admin" custom claim), return error if not
const isAdmin = context.auth.token.admin === true
if (!isAdmin) {
return { error: `Unauthorized.` }
}
return admin
.auth()
.listUsers()
.then((listUsersResult) => {
// go through users array, and deconstruct user objects down to required fields
const result = listUsersResult.users.map((user) => {
const { uid, email, photoURL, displayName, disabled } = user
return { uid, email, photoURL, displayName, disabled }
})
return { result }
})
.catch((error) => {
return { error: 'Error listing users' }
})
})
You can do it using admin.auth().listUsers
Here is the doc for this: https://firebase.google.com/docs/reference/admin/node/admin.auth.Auth.html#listusers
And an usage example: https://firebase.google.com/docs/auth/admin/manage-users#list_all_users
function listAllUsers(nextPageToken) {
// List batch of users, 1000 at a time.
admin.auth().listUsers(1000, nextPageToken)
.then(function(listUsersResult) {
listUsersResult.users.forEach(function(userRecord) {
console.log('user', userRecord.toJSON());
});
if (listUsersResult.pageToken) {
// List next batch of users.
listAllUsers(listUsersResult.pageToken);
}
})
.catch(function(error) {
console.log('Error listing users:', error);
});
}
// Start listing users from the beginning, 1000 at a time.
listAllUsers();
i will answer it simply as much as possible
just add the registered user to your data base by using the following code
you can also use shared prefernces to save the data locally but it won't be able for other user.
once you save the list of user in the database simply retrieve it from there using adapters
FirebaseDatabase.getInstance().getReference().child("my_user")
.child(task.getResult().getUser().getUid())
.child("username").setValue(autoCompleteTextView1.getText().toString());
Users list in python:
from firebase_admin import credentials, db, auth
cred = credentials.Certificate('\path\to\serviceAccountKey.json')
default_app = firebase_admin.initialize_app(cred, {
"databaseURL": "https://data_base_url.firebaseio.com"
})
users = auth.list_users()