Firebase 监听器在离开并返回屏幕后下载数据

Posted

技术标签:

【中文标题】Firebase 监听器在离开并返回屏幕后下载数据【英文标题】:Firebase listener downloads data after leaving and getting back to screen 【发布时间】:2021-10-27 11:57:36 【问题描述】:

我在我的应用程序中实现了一个聊天屏幕,下面的代码代表了代码的重要示例,我注意到关于数据使用的一些东西非常奇怪。该代码是一个稍长的代码示例,但我会在之后解释它。

    const CountryChat = props =>
        var chosenLanguage = useSelector(state => state.myLanguage.myLanguage);
        const countryId = props.navigation.getParam("countryId");//already upper case so no worries about correct firestore adress
        const countryName = props.navigation.getParam("countryName");
        const userId = useSelector(state => state.auth.userId);
        const [TItext, setTItext] = useState("");
        const [chatmessages, setChatMessages] = useState(() => []);//dummydata so FlatList wont crash because messages are empty during first renderprocess
        const [refreshFlatlist, setRefreshFlatList] = useState(false);
        const [myProfilePic, setMyProfilePic] = useState(null);
    
        useEffect(() => 
            downloadProfilePic();
            var loadnewmessages = firebase.firestore().collection("group_rooms").doc("group_rooms").collection(`$countryId`).orderBy("timestamp").limit(30).onSnapshot((snapshot)  => 
                var newmessages = [];
                var deletedmesssages = [];
                snapshot.docChanges().forEach((change) => 
                    if(change.type  === "added")
                        newmessages.push(
                            counter: change.doc.data().counter,
                            sender: change.doc.data().sender,
                            timestamp: change.doc.data().timestamp.toString(),
                            value: change.doc.data().value,
                            displayedTime: new Date(change.doc.data().displayedTime),
                            senderProfilePic: change.doc.data().senderProfilePic
                        )
                    ;
                    if(change.type  === "removed")
                        deletedmesssages.push(
                            counter: change.doc.data().counter,
                            sender: change.doc.data().sender,
                            timestamp: change.doc.data().timestamp.toString(),
                            value: change.doc.data().value,
                            displayedTime: new Date(change.doc.data().displayedTime),
                            senderProfilePic: change.doc.data().senderProfilePic
                        )
                    ;
    
                )
                if(newmessages.length > 0)
                    setChatMessages(chatmessages => 
                        return chatmessages.concat(newmessages)
                    );
                ;
                if(deletedmesssages.length > 0)
                    setChatMessages(chatmessages => 
                        var modifythisarray = chatmessages;
                        let index = chatmessages.map(e => e.timestamp).indexOf(`$deletedmesssages[0].timestamp`);
                        let pasttime = Date.now() - parseInt(modifythisarray[index].timestamp);
                        modifythisarray.splice(index, 1);
                        if(pasttime > 300000)
                            return chatmessages
                        else
                            return modifythisarray
                        
                    );
                    setRefreshFlatList(refreshFlatlist => 
                        //console.log("Aktueller Status von refresher: ", refreshFlatlist);
                        return !refreshFlatlist
                    );
                
                newmessages = [];
                deletedmesssages = [];
            );
    
            return () =>  //for removing listeners
                try
                    loadnewmessages();
                catch(error)console.log(error);
            
        , []);
    
    
        const pushMessagetoDB = async (filter, imageName) => 
            //sending message to the chatroom in Firestore
            if(filter == 1)
                await firebase.firestore().collection("group_rooms").doc("group_rooms").collection(`$countryId`).add(
                    "counter": 1,
                    "sender": userId,
                    "timestamp": Date.now(),
                    "value": TItext,
                    "displayedTime": (new Date()).toISOString(),
                    "senderProfilePic": myProfilePic
                )
                .then(() => 
                    console.log("Chat written in DB!");
                )
                .catch((error) => 
                    console.error("Error writing Chat into DB: ", error);
                );
            else
                await firebase.firestore().collection("group_rooms").doc("group_rooms").collection(`$countryId`).add(
                    "counter": 2,
                    "sender": userId,
                    "timestamp": Date.now(),
                    "senderProfilePic": myProfilePic,
                    "value": await firebase.storage().ref(`countrychatimages/$countryId/$imageName`).getDownloadURL().then((url) => 
                        return url
                    ).catch((error) =>  //incase something bad happened
                        console.log(error);
                    )
                )
                .then(() => 
                    console.log("Image passed to DB!");
                )
                .catch((error) => 
                    console.error("Error passing Image to DB: ", error);
                );
            
        ;

你可以在这里看到我的监听器loadnewmessages,它在我的 useEffect 中被调用。此侦听器下载聊天中最近的 30 条消息并将它们存储在一个状态中。聊天完美无缺,我什至可以发送消息(将文档存储在代表聊天的集合内的 firestore 上)。在我离开屏幕后,useEffect 中的return 被触发,我的监听器被取消。

我的问题是:我来回走了大约 4 次,我的收藏中有 6 条消息。完成此操作后,我关闭了我的应用程序并在 firebase 的“使用和计费”中检查了我的使用情况,突然发现我有大约 25 次读取。我希望我的听众只会下载一次包含文档的集合,并且即使我离开屏幕也会保留在手机上,而不是当我检查屏幕时我总是重新下载它,即在我在我的 firebase 控制台中看到我的使用情况后,我假设正在发生的事情。如果我启动我的应用并获得 100 或更多用户,我的账单就会以这种方式激增。

我知道我分离了我的监听器并重新启动它,但我希望 firebase 能够维护手机上已经加载的数据,所以我(如果不会写入新文件)我只读取 1 次,因为查询运行时没有加载任何新的数据。

有人可以向我解释我做错了什么或者我可以如何改进我的代码以减少读取吗?如何更改我的代码以使其保持高效并且不下载已加载的数据?将读取保持在低水平对我来说真的很重要,我在控制它方面遇到了很大的问题,而且我的钱非常有限。

【问题讨论】:

【参考方案1】:

这是预期的行为。当您切换页面/活动时,侦听器将关闭。如文档中所述,侦听器将在重新连接时(就像第一次连接一样)获取查询中指定的所有匹配文档:

使用您提供的回调的初始调用会立即使用单个文档的当前内容创建文档快照。然后,每次内容更改时,另一个调用会更新文档快照。

你可以试试:

启用离线持久性,缓存您的应用正在使用的 Cloud Firestore 数据的副本,以便您的应用可以在设备离线时访问数据。如果文档是从缓存中获取的,那么您将不会被收取读取费用。但是我不确定这是否是您用例的最佳选择。

将到目前为止获取的消息存储在该平台的本地存储中,然后使用侦听器查询消息之后发送的消息。如果删除任何消息,您必须从本地存储中删除消息。

const messagesRef = db..collection("group_rooms").doc("group_rooms").collection(`$countryId`);

return messagesRef.doc("last_local_msg_id").get().then((doc) => 
    // Get all messages sent after last local msg
    const newMessagesQuery = messagesRef
        .orderBy("timestamp")
        .startAt(doc)
        .limit(30);
);

例如使用异步存储很合适,即使增加异步存储的内存大小也不是问题,因此可以存储更多数据,因此可以像showed here.那样进行更多聊天

【讨论】:

我真的很想使用持久化功能,但有人向我解释它是默认启用的(显然不是......),甚至在官方文档中写道:“对于 androidios,默认启用离线持久化。”从这里得到它:firebase.google.com/docs/firestore/manage-data/… @KubaghettothefreshTestobun 这完全取决于您使用哪种方法。在我看来,您自己在本地存储消息可以让您更好地控制所存储的内容。 问题是我可以使用 redux 存储,但是当我关闭并重新打开我的应用程序时数据会丢失。另一种解决方案是使用 async-storage,但它的限制只有 6MB,这不适合此目的。 @KubaghettothefreshTestobun 6 MB 似乎存储了大量消息。您可以只存储每个聊天或其他内容的最后 100 条消息,然后根据需要向数据库发出请求。还是比每次打 25 个电话要好? @KubaghettothefreshTestobun 您可以更好地控制存储的内容以及使用异步存储可以删除的内容。

以上是关于Firebase 监听器在离开并返回屏幕后下载数据的主要内容,如果未能解决你的问题,请参考以下文章

Firebase 删除用户问题

如何仅在用户下载应用程序后获取设备令牌?

使用 firebase 动态链接下载应用并删除并重新下载会在链接中返回一个值

删除/关闭 Firebase 侦听器

如何将多个图像上传到 Firebase 存储并返回多个下载 URL

SwiftUI + Firebase - 监听器不监听变化?