简体   繁体   English

使用 Firebase 的聊天应用程序:收到新消息时收到通知 - Android

[英]Chat App using Firebase: Get notification when new message received - Android

I am developing a chat app using Firebase Realtime Database.我正在使用 Firebase 实时数据库开发聊天应用程序。 I have been able to send and receive messages properly.我已经能够正确发送和接收消息。 Now, I want to implement notification whenever new message is received.现在,我想在收到新消息时实现通知。 For that, I have created a Service which listens to database changes using ChildEventListener and creates notification.为此,我创建了一个Service ,它使用ChildEventListener侦听数据库更改并创建通知。 The problem is that I am creating notification in onChildAdded method and this method fires both for existing node in database and new one.问题是我在onChildAdded方法中创建通知,并且此方法同时为数据库中的现有节点和新节点触发。 This is causing notification to be created multiple times for same message whenever user navigate back and forth from app.每当用户从应用程序来回导航时,这会导致为同一消息多次创建通知。

Here is how I am implementing it:这是我如何实施它:

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) {

            }
        });

How can I implement notifications in the way it works in other chat applications like whatsapp, etc.??我如何以它在其他聊天应用程序(如 whatsapp 等)中的工作方式实现通知??

The correct way to do this is to have a push data message to the client with the chat when there is a new chat sent to them.正确的方法是在有新聊天发送给客户端时,向客户端发送一条推送数据消息。

This blog post explains how to accomplish this.这篇博文解释了如何实现这一点。 Basically you need to set up a server to listen to the chat firebase ref and send push notifications when it is updated.基本上,您需要设置一个服务器来收听聊天 firebase ref 并在更新时发送推送通知。 This way your clients can be in the app or out of the app and still get the push.通过这种方式,您的客户可以在应用程序中或应用程序外,仍然可以得到推送。

If you use a service there are a number of potential issues.如果您使用服务,则存在许多潜在问题。

First of all you will have to keep the phone awake.首先,您必须保持手机处于唤醒状态。 This will drain the battery.这会耗尽电池电量。

Second, Android can kill your background service at any time, so your app may stop working suddenly.其次,Android 可以随时杀死您的后台服务,因此您的应用可能会突然停止工作。

Third, with Doze mode Android will block network activity and stop your app from running in the background.第三,在 Doze 模式下,Android 将阻止网络活动并阻止您的应用程序在后台运行。

A better answer occurred to me:我想到了一个更好的答案:

What you want in this case isn't necessarily to know new messages.在这种情况下,您想要的不一定是了解新消息。 It's to know UNREAD messages.它是了解未读消息。

Set a "read" flag in the ChatMessage object (or conversely, have a value somewhere that gives the timestamp or ID of the most recent read message).在 ChatMessage 对象中设置一个“读取”标志(或者相反,在某处设置一个值,给出最近读取消息的时间戳或 ID)。

Now whenever onChildAdded is fired, check to see if read == false.现在每当 onChildAdded 被触发时,检查是否 read == false。 If so, show a notification that the message is unread (remember to update the notification if it exists, so only one notification will be displayed and it will show the most recent one--oh, and also remember to remove the notification when the child is changed to read.)如果是,显示消息未读的通知(如果有记得更新通知,这样只会显示一个通知,并且会显示最近的一个--哦,还有记得在孩子的时候去掉通知)改为阅读。)

If the user is using your app on multiple devices, it will correctly check the read state.如果用户在多个设备上使用您的应用程序,它将正确检查读取状态。 If he reads the latest message on a phone and then goes to the tablet, it will not show that new message notification.如果他在手机上阅读最新消息,然后转到平板电脑,则不会显示新消息通知。

If you wanted to, you could even use this functionality to indicate that the recipient read your message to them.如果您愿意,您甚至可以使用此功能来指示收件人已将您的消息读给他们。

How do you know when it is read?你怎么知道什么时候读? Perhaps simply when you add it on the screen.也许只是当您将其添加到屏幕上时。 Perhaps you make sure it is in view (not scrolled off the screen) and is visible for several seconds.也许您确保它在视图中(没有从屏幕上滚动)并且可以看到几秒钟。

Have you tried addValueEventListener?你试过 addValueEventListener 吗?

https://www.firebase.com/docs/android/guide/retrieving-data.html#section-start https://www.firebase.com/docs/android/guide/retrieving-data.html#section-start

// Get a reference to our posts
Firebase ref = new Firebase("https://docs-examples.firebaseio.com/web/saving-data/fireblog/posts");
// Attach an listener to read the data at our posts reference
ref.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot snapshot) {
        System.out.println(snapshot.getValue());
    }
    @Override
    public void onCancelled(FirebaseError firebaseError) {
        System.out.println("The read failed: " + firebaseError.getMessage());
    }
});

"This method will be called anytime new data is added to our Firebase reference, and we don't need to write any extra code to make this happen." “只要有新数据添加到我们的 Firebase 参考中,就会调用此方法,我们不需要编写任何额外的代码来实现这一点。”

EDIT: "It is triggered once with the initial data and again every time the data changes."编辑:“使用初始数据触发一次,每次数据更改时再次触发。” I haven't tried it, but my thought would be to set a flag when the method fires the first time.我还没有尝试过,但我的想法是在方法第一次触发时设置一个标志。 Then whenever the method and that flag is set (in other words, the second time, third time, fourth time, etc.) get the most recent object from the snapshot.然后每当方法和该标志被设置时(换句话说,第二次、第三次、第四次等)从快照中获取最新的对象。

A combination of the answer of @Chad Shultz and your original code, with some database structural additions. @Chad Shultz 的答案和您的原始代码的组合,以及一些数据库结构添加。

You will, as you originally intended, use a Query您将按照最初的意图使用 Query

chatMsgsRef.orderByChild(...)

However, change the data structure to have the following layout但是,将数据结构更改为具有以下布局

>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

So whenever a message is sent, it gets pushed to the "messages" node.因此,无论何时发送消息,它都会被推送到“消息”节点。 Next, the system gets all the "participants", and pushes them under the "message_read_states" node for that chat/message, with a value of false for everyone except the sender.接下来,系统获取所有“参与者”,并将它们推送到该聊天/消息的“message_read_states”节点下,对于除发件人之外的所有人,其值为 false。 When someone reads the message, they change their value to true there.当有人阅读消息时,他们会在那里将其值更改为 true。

Now the Service needs to determine for which messages to notify a user.现在,服务需要确定要通知用户的消息。 The Service will place a listener on message_read_states/chat_X, and order it by the value of the child field "userID_X" (depending on who the current user is). Service 将在 message_read_states/chat_X 上放置一个监听器,并根据子字段“userID_X”的值对其进行排序(取决于当前用户是谁)。 We'll make the Query only return the message ID's, for each chat, for which there is a value "userID_X:false" under it我们将使查询只返回消息 ID,对于每个聊天,其下有一个值“userID_X:false”

So in this example, for "userID_002", the query will return "msg_001" , but for "userID_001" it will return nothing, because the required value is not found for that key (he/she was the sender).因此,在此示例中,对于“userID_002”,查询将返回“msg_001”,但对于“userID_001”,它将不返回任何内容,因为找不到该键所需的值(他/她是发件人)。

The Query will be constructed as follows, inside your Service's onStartCommand:查询将在您的服务的 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) {

            }
        });
    }

Now of course it's very likely a user will be involved in multiple chats.当然,用户很可能会参与多个聊天。 You'll have to iterate over all the chats associated with the user, and implement a Query for each chat.您必须遍历与用户关联的所有聊天,并为每个聊天实现一个查询。

Source : I'm currently working on a Firebase app with a chat system来源:我目前正在开发带有聊天系统的 Firebase 应用

YOU CAN DO IT FOR FREE AND WITHOUT SERVICES!!您可以免费且无需服务!

What Shmuel says is the right way to do it. Shmuel 所说的是正确的做法。 However, to do what is described in that blog post he links, you need Cloud functions.但是,要执行他链接的博客文章中描述的操作,您需要云功能。 And they are not free on Firebase, you have to pay.而且它们在 Firebase 上不是免费的,您必须付费。

What the others describe in their answers is working but it's not good because it is not reliable (eg if the user restarts his phone it won't work) and such background services slow down the device by a lot.其他人在他们的回答中描述的内容有效,但效果不佳,因为它不可靠(例如,如果用户重新启动手机,它将无法工作)并且此类后台服务会大大降低设备速度。


SOLUTION:解决方案:

You can pretty much do what you would do in these cloud functions on the user backend.您几乎可以在用户后端执行这些云功能中的操作。

This is also not perfect because you need to store sensitive information in the code.这也不完美,因为您需要在代码中存储敏感信息。 So malicious users could get the information and abuse it for sending notifications to your users themselves.因此,恶意用户可以获取信息并滥用它来向您的用户自己发送通知。

But it is free, reliable and doesn't slow down the device at all.但它是免费的、可靠的,并且根本不会减慢设备的速度。

Below is the implementation.下面是实现。


Module:app dependencies模块:应用程序依赖项

implementation 'com.squareup.retrofit2:retrofit:2.6.0'
implementation 'com.squareup.retrofit2:converter-gson:2.6.0'

ApiClient客户端

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;
    }
}

ApiInterface接口

public interface ApiInterface {

    @Headers({"Authorization: key=" + ConstantKey.SERVER_KEY, "Content-Type:application/json"})
    @POST("fcm/send")
    Call<ResponseBody> sendNotification(@Body RootModel root);
}

RootModel根模型

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;
    }
}

NotificationModel通知模型

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;
    }

}

DataModel数据模型

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;
    }

}

Send notification by using this method使用此方法发送通知

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) {

        }
    });
}

You can get ConstantKey.SERVER_KEY in your Firebase Project settings in the Messaging tab.您可以在“消息”选项卡的 Firebase 项目设置中获取ConstantKey.SERVER_KEY

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 使用Firebase在Android上收到新消息时,如何更新我的聊天窗口? - How can I update my chat window when a new message is received on Android using Firebase? 收到新消息Firebase Android时获取通知 - Get notification when there is a new message Firebase Android 当我的 android App 收到 firebase 消息通知,然后关机 - When my android App get firebase message notification, then shutting down 如何使用本地通知在 firebase 聊天应用程序上获取通知? - How to get notification on firebase chat app using local notification? Firebase 新聊天消息推送通知 - Firebase push notification on new chat message Android 聊天应用,收到新消息时更新聊天 - Android Chat app, update the chat when you receive a new message Android Firebase:当应用程序处于前台时如何从Firebase获取通知消息的消息文本组件 - Android Firebase : how get the message text component of notification message from firebase when app is in foreground Firebase 在 android 中从后台删除应用程序时未收到推送通知 - Firebase push notification not received when app remove from background in android 收到的Firebase通知启动并关闭Android应用 - Received Firebase Notification starts and closes android app Android Firebase通知消息已收到,但未显示通知提示? - Android Firebase Notification message received but notification prompt is not showing?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM