我正在使用Firebase实时数据库开发聊天应用。我能够正确地发送和接收消息。现在,我想在收到新消息时实现通知。为此,我创建了一个Service
,它使用ChildEventListener
监听数据库更改并创建通知。问题是我在onChildAdded
方法中创建通知,并且此方法为数据库中的现有节点和新节点触发。这导致每当用户从应用程序来回导航时,为同一消息多次创建通知。
以下是我如何实施它:
chatMsgsRef.orderByChild(FirebaseDBKeys.LOCATION_LAST_UPDATED).addChildEventListener(new ChildEventListener() {
@Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
ChatMessage message = dataSnapshot.getValue(ChatMessage.class);
if (!message.getSenderId().equals(currentUserId)) {
mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(NotificationsService.this)
.setSmallIcon(R.drawable.message)
.setContentTitle("New Message from " + message.getReceipientName())
.setContentText(message.getMessage())
.setOnlyAlertOnce(true)
.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION));
mBuilder.setAutoCancel(true);
mBuilder.setLocalOnly(false);
mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
}
}
@Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
}
@Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
@Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
@Override
public void onCancelled(DatabaseError databaseError) {
}
});
如何以其在whatsapp等其他聊天应用程序中的工作方式实现通知。??
答案 0 :(得分:11)
执行此操作的正确方法是在发送新聊天时向客户端发送推送数据消息。
这blog post解释了如何实现这一目标。 基本上,您需要设置一个服务器来监听聊天firebase ref并在更新时发送推送通知。通过这种方式,您的客户可以在应用程序中或在应用程序之外,仍然可以获得推动。
如果您使用某项服务,则存在许多潜在问题。
首先,你必须让手机保持清醒状态。这会耗尽电池。
其次,Android可以随时终止您的后台服务,因此您的应用可能会突然停止工作。
第三,使用Doze模式,Android将阻止网络活动并阻止您的应用在后台运行。
答案 1 :(得分:4)
我发现了一个更好的答案:
在这种情况下,您想要的是不必知道新消息。了解UNREAD消息。
设置"读取" ChatMessage对象中的标志(或相反,在某处提供一个值,用于给出最近读取消息的时间戳或ID)。
现在每当触发onChildAdded时,检查是否读取== false。如果是这样,请显示消息未读的通知(记得更新通知,如果它存在,所以只会显示一个通知,它会显示最近的通知 - 哦,还记得在孩子时删除通知改为阅读。)
如果用户在多台设备上使用您的应用,则会正确检查读取状态。如果他在手机上阅读最新消息然后转到平板电脑,则不会显示新消息通知。
如果您愿意,您甚至可以使用此功能来指示收件人向您发送消息。
你怎么知道什么时候读?也许只是当你在屏幕上添加它时。也许您确保它在视野中(不在屏幕上滚动)并且可以看几秒钟。
答案 2 :(得分:3)
您是否尝试过addValueEventListener?
https://www.firebase.com/docs/android/guide/retrieving-data.html#section-start
response.message
"只要将新数据添加到我们的Firebase参考中,就会调用此方法,并且我们不需要编写任何额外的代码来实现此目的。"
编辑: "初始数据触发一次,每次数据更改时触发一次。" 我还没有尝试过,但我的想法是在方法第一次触发时设置一个标志。然后,只要设置了方法和该标志(换句话说,第二次,第三次,第四次等),就从快照中获取最新的对象。
答案 3 :(得分:2)
@Chad Shultz和原始代码的答案的组合,以及一些数据库结构添加。
您将按照您的意图使用查询
chatMsgsRef.orderByChild(...)
但是,请将数据结构更改为具有以下布局
>root
> users
> userID_001
> chats
> chat_001:true
> userID_002
> chats
> chat_001:true
> chats
> chat_001
> participants
> userID_001:true
> userID_002:true
> messages
> msg_001
> sender:"userID_001"
> text:"some message"
> time:"some time"
> message_read_states
> chat_001
> msg_001
> userID_001:true
> userID_002:false
因此,无论何时发送消息,它都会被推送到“消息”节点。 接下来,系统获取所有“参与者”,并将其推送到该聊天/消息的“message_read_states”节点下,除了发件人之外的所有人都为false。 当有人阅读邮件时,他们会将值更改为true。
现在,服务需要确定要通知用户的消息。 服务将在message_read_states / chat_X上放置一个监听器,并按子字段“userID_X”的值对其进行排序(取决于当前用户是谁)。 我们将使查询仅返回每个聊天的消息ID,其下面有一个值“userID_X:false”
因此,在此示例中,对于“userID_002”,查询将返回“msg_001”,但对于“userID_001”,它将不返回任何内容,因为找不到该密钥所需的值(他/她是发件人)。
Query将在Service的onStartCommand中构建如下:
//TODO: Get the ID of the chat the user is taking part in
String chatID = "chat_001";
// Check for new messages
FirebaseUser currentUser = FirebaseAuth.getInstance().getCurrentUser();
if (currentUser != null){
String UID = currentUser.getUid();
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
Query query = rootRef.child("message_read_states").child(chatID).orderByChild(UID).equalTo(false);
query.addChildEventListener(new ChildEventListener() {
@Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
String messageID = dataSnapshot.getKey();
//TODO: Handle Notification here, using the messageID
// A datasnapshot received here will be a new message that the user has not read
// If you want to display data about the message or chat,
// Use the chatID and/or messageID and declare a new
// SingleValueEventListener here, and add it to the chat/message DatabaseReference.
}
@Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
String messageID = dataSnapshot.getKey();
//TODO: Remove the notification
// If the user reads the message in the app, before checking the notification
// then the notification is no longer relevant, remove it here.
// In onChildAdded you could use the messageID(s) to keep track of the notifications
}
@Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
@Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
@Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
当然,用户很可能会参与多个聊天。您将必须遍历与用户关联的所有聊天,并为每个聊天实施查询。
来源:我目前正在开发一个带聊天系统的Firebase应用
答案 4 :(得分:0)
Shmuel 所说的是正确的做法。但是,要执行他链接的博客文章中描述的操作,您需要云功能。而且它们在 Firebase 上不是免费的,您必须付费。
其他人在他们的回答中描述的内容有效,但效果不佳,因为它不可靠(例如,如果用户重新启动手机,它将无法工作)并且此类后台服务会大大降低设备速度。
解决方案:
您几乎可以在用户后端执行这些云功能中的操作。
这也不完美,因为您需要在代码中存储敏感信息。因此,恶意用户可能会获取这些信息并滥用它来向您的用户自己发送通知。
但它是免费的、可靠的,并且根本不会减慢设备的速度。
下面是实现。
implementation 'com.squareup.retrofit2:retrofit:2.6.0'
implementation 'com.squareup.retrofit2:converter-gson:2.6.0'
public class ApiClient {
private static final String BASE_URL = "https://fcm.googleapis.com/";
private static Retrofit retrofit = null;
public static Retrofit getClient() {
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}
public interface ApiInterface {
@Headers({"Authorization: key=" + ConstantKey.SERVER_KEY, "Content-Type:application/json"})
@POST("fcm/send")
Call<ResponseBody> sendNotification(@Body RootModel root);
}
public class RootModel {
@SerializedName("to") // "to" changed to token
private String token;
@SerializedName("notification")
private NotificationModel notification;
@SerializedName("data")
private DataModel data;
public RootModel(String token, NotificationModel notification, DataModel data) {
this.token = token;
this.notification = notification;
this.data = data;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public NotificationModel getNotification() {
return notification;
}
public void setNotification(NotificationModel notification) {
this.notification = notification;
}
public DataModel getData() {
return data;
}
public void setData(DataModel data) {
this.data = data;
}
}
public class NotificationModel {
private String title;
private String body;
public NotificationModel(String title, String body) {
this.title = title;
this.body = body;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
public class DataModel {
private String name;
private String age;
public DataModel(String name, String age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
private void sendNotificationToUser(String token) {
RootModel rootModel = new RootModel(token, new NotificationModel("Title", "Body"), new DataModel("Name", "30"));
ApiInterface apiService = ApiClient.getClient().create(ApiInterface.class);
retrofit2.Call<ResponseBody> responseBodyCall = apiService.sendNotification(rootModel);
responseBodyCall.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(retrofit2.Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) {
Log.d(TAG,"Successfully notification send by using retrofit.");
}
@Override
public void onFailure(retrofit2.Call<ResponseBody> call, Throwable t) {
}
});
}
您可以在“消息”选项卡的 Firebase 项目设置中获取 ConstantKey.SERVER_KEY
。