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

rbl8hiat  于 2022-11-25  发布在  Android
关注(0)|答案(5)|浏览(213)

我正在开发一个使用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等??

r8uurelv

r8uurelv1#

正确的做法是,当有新聊天发送给客户端时,向客户端推送数据消息。
这个blog post解释了如何实现这个功能。基本上你需要设置一个服务器来监听聊天firebase ref,并在更新时发送推送通知。这样你的客户端可以在应用程序中或应用程序外,仍然可以获得推送。
如果您使用某项服务,则会出现许多潜在问题。
首先,你必须让手机保持清醒。这会耗尽电池。
第二,Android可以随时杀死你的后台服务,所以你的应用程序可能会突然停止工作。
第三,在打盹模式下,Android将阻止网络活动,并阻止您的应用在后台运行。

fsi0uk1n

fsi0uk1n2#

我想到了一个更好的答案:
在这种情况下,你需要的并不一定是知道新消息,而是知道未读消息。
在ChatMessage对象中设置一个“read”标志(或者相反,在某个位置设置一个值,给出最近读取的消息的时间戳或ID)。
现在,每当onChildAdded被触发时,检查是否read == false。如果是,则显示消息未读的通知(如果存在通知,请记住更新通知,这样只会显示一个通知,它将显示最近的一个通知--哦,还记得在子对象更改为read时删除通知)。
如果用户在多台设备上使用你的应用,它会正确检查阅读状态。如果他在手机上阅读了最新的消息,然后转到平板电脑,它不会显示新消息通知。
如果愿意,您甚至可以使用此功能来指示收件人已将您的邮件读给他们听。
你怎么知道它什么时候被阅读呢?也许仅仅是当你把它添加到屏幕上的时候。也许你确保它在视图中(不是滚动出屏幕)并且在几秒钟内是可见的。

up9lanfz

up9lanfz3#

是否尝试过添加值事件监听器?
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());
    }
});

“只要有新数据添加到Firebase引用中,就会调用此方法,我们不需要编写任何额外的代码来实现这一点。”
EDIT:“它在初始数据时触发一次,每次数据改变时都会再次触发。”我还没有尝试过,但我的想法是在方法第一次触发时设置一个标志。然后每当方法和标志被设置时(换句话说,第二次、第三次、第四次等等),从快照中获取最新的对象。

eoxn13cs

eoxn13cs4#

@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

因此,无论何时发送消息,它都会被推送到“messages”节点。接下来,系统获取所有“参与者”,并将他们推送到该聊天/消息的“message_read_states”节点下,除发送者外,每个人的值都为false。当有人阅读消息时,他们会将其值更改为true。
现在,服务需要确定要通知用户的消息。服务将在message_read_states/chat_X上放置一个监听器,并根据子字段“userID_X”的值对其进行排序(取决于当前用户是谁)。我们将使查询只返回每个聊天的消息ID,对于每个聊天,消息ID下都有一个值“userID_X:false
因此,在本例中,对于“userID_002”,查询将返回“msg_001”,但对于“userID_001”,它将不返回任何内容,因为未找到该键所需的值(他/她是发送者)。
在服务的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) {

            }
        });
    }

当然,用户很可能会参与多个聊天,您必须遍历与该用户相关的所有聊天,并为每个聊天实现一个Query。
来源:我目前正在开发一个带有聊天系统的Firebase应用程序

cnwbcb6i

cnwbcb6i5#

免费且不含服务

您可以在后端完成所有这些工作,而无需云功能和成本。
但是,我强烈建议不要这样做,因为你必须把你的项目的服务器密钥放在代码中,这是一个巨大的安全问题。设置一个云函数来做这件事是非常简单和安全的。

模块:应用程序依赖项

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

Api客户端

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

Api接口

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

相关问题