Firebase deleted data still there - firebase

i have a behavior that I can't understand.
I delete a node on firebase database and I still receive the data during observing .value. But in firebase database the node is deleted.
I have a node called users_shoppinglists. Here are all id's of the users nodes to observe stored. Then I iterate all the id's to observe and call a function that observes each ID.
When I need to delete a list I update a node called status on the shoppinglists node and delete all to this list related data via cloud functions.
But the data is still received during observe. It seems I receive the data again before it is completely deleted.
Iterate all id's:
func ObserveAllList() -> Void{
if currentUser == nil { return }
self.ShowActivityIndicator()
ref.child("users_shoppinglists").child(currentUser!.id!).observe(.value, with: { (usersListsSnap) in
if usersListsSnap.value is NSNull { self.HideActivityIndicator(); return }
for listSnap in usersListsSnap.children {
let list = listSnap as! DataSnapshot
self.ObserveSingleList(listID: list.key)
}
}) { (error) in
NSLog(error.localizedDescription)
let title = String.OnlineFetchRequestError
let message = error.localizedDescription
self.ShowAlertMessage(title: title, message: message)
return
}
}
Call function to observe each ID:
func ObserveSingleList(listID:String) -> Void {
self.ShowActivityIndicator()
ref.child("shoppinglists").child(listID).observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.value is NSNull { self.HideActivityIndicator(); return }
//Read listData
var newList = ShoppingList()
newList.id = snapshot.key
newList.name = snapshot.childSnapshot(forPath: "listName").value as? String
newList.owneruid = snapshot.childSnapshot(forPath: "owneruid").value as? String
newList.relatedStore = snapshot.childSnapshot(forPath: "relatedStore").value as? String
//Read List items
self.ref.child("listItems").child(listID).observe(.value, with: { (itemSnap) in
var newItems = [ShoppingListItem]()
for items in itemSnap.children {
let item = items as! DataSnapshot
var newItem = ShoppingListItem()
newItem.id = item.key
newItem.listID = listID
newItem.isSelected = item.childSnapshot(forPath: "isSelected").value as? Bool
newItem.itemName = item.childSnapshot(forPath: "itemName").value as? String
newItem.sortNumber = item.childSnapshot(forPath: "sortNumber").value as? Int
newItems.append(newItem)
}
newList.items = newItems
//Read List members
self.ref.child("shoppinglist_member").child(listID).observe(.value, with: { (memberSnap) in
var newMembers = [ShoppingListMember]()
for members in memberSnap.children {
let member = members as! DataSnapshot
var m = ShoppingListMember()
m.memberID = member.key
m.status = member.value as? String
newMembers.append(m)
}
newList.members = newMembers
DispatchQueue.main.async {
if let index = allShoppingLists.index(where: { $0.id == listID }){
allShoppingLists[index] = newList
} else {
allShoppingLists.append(newList)
}
self.HideActivityIndicator()
NotificationCenter.default.post(name: Notification.Name.ShoppingBuddyListDataReceived, object: nil, userInfo: nil)
}
}, withCancel: { (error) in
self.HideActivityIndicator()
NSLog(error.localizedDescription)
let title = String.OnlineFetchRequestError
let message = error.localizedDescription
self.ShowAlertMessage(title: title, message: message)
return
})
}, withCancel: { (error) in
self.HideActivityIndicator()
NSLog(error.localizedDescription)
let title = String.OnlineFetchRequestError
let message = error.localizedDescription
self.ShowAlertMessage(title: title, message: message)
return
})
}) { (error) in
self.HideActivityIndicator()
NSLog(error.localizedDescription)
let title = String.OnlineFetchRequestError
let message = error.localizedDescription
self.ShowAlertMessage(title: title, message: message)
return
}
}
Cloud function:
//****************************************************************************************************************/
// Handles an action when status value changed in users_shoppinglists node
//****************************************************************************************************************/
exports.handle_ListStatusUpdate = functions.database.ref('/shoppinglists/{listID}').onUpdate(event => {
var listData = event.data.val()
console.log('Status', listData.status)
//handle deleted by owner
if (String(listData.status) == 'deleted by owner') {
//Get all members to delete the list on their users_shoppinglists node
return admin.database().ref('shoppinglist_member').child(event.params.listID).once('value').then(listMember => {
var promises = []
listMember.forEach(function (member) {
promises.push(admin.database().ref('users_shoppinglists').child(member.key).child(event.params.listID).set(null).then(() => {
return admin.database().ref('shoppinglist_member').child(event.params.listID).set(null).then(() => {
// delete the original shopping list
return admin.database().ref('shoppinglists').child(event.params.listID).set(null).then(() => {
return admin.database().ref('listItems').child(event.params.listID).set(null).then(() => {
})
})
})
}))
})
})
}
});/*********************************************************************************************************** */

Had this issue on Simulator. It was not only .value but .childRemoved and .childChanged were not triggered at all (only .childAdded was working).
Tried on iPhone and it worked. Then I made "Erase All Content And Settings..." to Simulator and it started to work again on Simulator too.
My bet is that firebase cache gets dirty during development, while you add or remove observers in code and probably change structure in database and at some point it just stops reacting appropriately.

Related

SWIFT UI, when the content of one of my views updates from current view, the current view exits by itself

I have a chat in which there are 4 views.
Settings view
Recent Messages view, where you can see the last message you have received/sent
Contacts view where you just see the contacts you have
Chat log view
When I send message in chat log view, the content in Recent Messages is updated, because the last message is changed, when updating my chat log view exits to main view
Here is my Views:
the main view with tab bar from where 3 other views is accessed
Messages view: where the last messages is shown
Chat Log view
the chat log view exits when I do handleSend function in ChatLogViewModel
struct MainView: View {
#StateObject var vm = MainMessagesViewModel() //view model variable
#State private var isPresented = false //bool variable to show/hide login view
var body: some View {
NavigationView {
TabView(selection: .constant(0)) {
ContactsView()
.tabItem {
Label("მეგობრები", systemImage: "person.2")
}
.tag(0)
MessagesView().environmentObject(vm)
.tabItem {
Label("ჩატები", systemImage: "message")
}
.tag(1)
SettingsView().environmentObject(vm)
.tabItem {
Label("დაყენებები", systemImage: "gear")
}
.tag(2)
}
.fullScreenCover(isPresented: $isPresented, onDismiss: nil){ //iOS 16 style to call a view with #published variable (#state needed)
LogInView(didCompleteLogin: {
self.vm.isCurrentlyLogOut = false
self.vm.fetchCurrentUser()
})
}
.onReceive(vm.$isCurrentlyLogOut) { isCurrentlyLogOut in
self.isPresented = isCurrentlyLogOut
self.vm.fetchCurrentUser()
}
.onChange(of: isPresented) { isCurrentlyLogOut in
self.vm.isCurrentlyLogOut = isCurrentlyLogOut
self.vm.fetchCurrentUser()
}
}
}
}
class MainMessagesViewModel: ObservableObject {
#Published var user : User? //current user (optional because is nil on start)
#Published var isCurrentlyLogOut = false
#Published var recentMessages = [RecentMessage]() //array containing recent messages user has sent
private var firestoreListener : ListenerRegistration?
init() {
DispatchQueue.main.async {
self.isCurrentlyLogOut = FirebaseManager.shared.auth.currentUser?.uid == nil //figure out if there is no user logged in
}
fetchCurrentUser() //fetch a logged in user
fetchRecentMessages()
}
func fetchCurrentUser() { //fetching the user variable inside vm
guard let uid = FirebaseManager.shared.auth.currentUser?.uid else { return } //if there is a uid available create uid variable
FirebaseManager.shared.firestore.collection("users").document(uid).getDocument { snapshot, err in //retrieve from users/uid/ user's data, which is stored in snapshot
if let err = err { //error handler
print("Failed to fetch current user: ", err)
return
}
guard let data = snapshot?.data() else { return } //create a data variable if there is a data in snapshot
self.user = .init(data: data) //vm's user is initialized with struct's init()
}
}
private func fetchRecentMessages() {
guard let uid = FirebaseManager.shared.auth.currentUser?.uid else {return}
FirebaseManager.shared.firestore
.collection("recent_messages")
.document(uid)
.collection("messages")
.addSnapshotListener { querySnapshot, err in
if let err = err {
print(err)
return
}
querySnapshot?.documentChanges.forEach({ change in
let docId = change.document.documentID
let data = change.document.data()
if let index = self.recentMessages.firstIndex(where: {rm in
return rm.documentId == docId
}) {
self.recentMessages.remove(at: index)
}
self.recentMessages.insert(.init(documentId: docId, data: data), at: 0)
})
}
}
}
//MARK: - CHAT LOG VIEW
struct ChatLogView: View {
#ObservedObject var vm: ChatLogViewModel //view model variable
let interlocutor: User? //variable for interlocutor (provided from main view)
init(interlocutor: User?) { //initialize interlocutor
self.interlocutor = interlocutor
self.vm = .init(interlocutor: interlocutor) //initialize view model using interlocutor
}
var body: some View {
VStack{
messagesView
}
.navigationTitle(interlocutor?.email ?? "")
}
//MARK: - MESSAGES VIEW
private var messagesView: some View {
ScrollView {
ScrollViewReader { scrollViewProxy in
VStack {
ForEach(vm.chatMessages) { message in
VStack {
if message.fromId == FirebaseManager.shared.auth.currentUser?.uid{
HStack {
Spacer()
HStack {
Text(message.text)
.foregroundColor(.white)
}
.padding(.vertical, 5)
.padding(.horizontal, 15)
.background(Color.blue)
.cornerRadius(25)
}
.padding(.horizontal)
} else {
HStack {
HStack {
Text(message.text)
.foregroundColor(.black)
}
.padding(.vertical, 5)
.padding(.horizontal, 15)
.background(Color(.init(white: 0.94, alpha: 1)))
.cornerRadius(25)
Spacer()
}
.padding(.horizontal)
}
}
}
HStack {
Spacer()
}
.id("empty")
}
.onReceive(vm.$count) { _ in //when count of messages changes
withAnimation(.easeOut(duration: 0.5)) { //perform animation to scroll to the bottom of view
scrollViewProxy.scrollTo("empty", anchor: .bottom)
}
}
}
}
.safeAreaInset(edge: .bottom) { //insert a view to the bottom
chatBottomBar
}
}
//MARK: - CHAT BOTTOM BAR VIEW
private var chatBottomBar: some View {
HStack {
Image(systemName: "paperclip")
.padding(.leading, 5)
TextField("Message", text: $vm.chatText)
.padding(.vertical, 5)
.padding(.horizontal, 15)
.background(Color.white)
.cornerRadius(25)
Button {
vm.handleSend()
} label: {
Image(systemName: "arrow.up.circle.fill")
.font(.title)
}
}
.padding(.horizontal, 5)
.padding(.vertical, 7)
.background(Color(.init(white: 0.95, alpha: 0.99)))
}
}
class ChatLogViewModel: ObservableObject {
let interlocutor : User? //user whom you send a message
#Published var chatText = "" //var for a text of message
#Published var chatMessages = [ChatMessage]() //array of messages
#Published var count = 0 //var to track when a message is added to scroll down
init (interlocutor : User?) {
self.interlocutor = interlocutor //init a interlocutor using a interlocutor from contacts view
fetchMessages() //fetch messages from firestore to the app
}
func fetchMessages() {
guard let fromId = FirebaseManager.shared.auth.currentUser?.uid else {return} //create a fromID using current user's if available
guard let toId = interlocutor?.uid else {return} //create a toID using interlocutor's id
FirebaseManager.shared.firestore.collection("messages") //retrieve message data from path /messages/fromId/toId/ (ordered by time)
.document(fromId)
.collection(toId)
.order(by: "timestamp")
.addSnapshotListener { querySnapshot, err in //continiously listen to selected folder, the data will be in querySnapshot
if let err = err { //error handler
print("Failed to listen messages: ", err)
}
querySnapshot?.documentChanges.forEach({ change in //if there is a change of documents in querySnapshot
if change.type == .added {
let data = change.document.data() //create data variable
let docId = change.document.documentID //create docId variable
let chatMessage = ChatMessage(documentId: docId, data: data) //create chatMessage variable (initilize it with stucts's init using docId and data)
self.chatMessages.append(chatMessage) //add a message to messages array
}
})
DispatchQueue.main.async { //if message is added increase messages count (for scroll to bottom)
self.count += 1
}
}
}
func handleSend() { //function to send a message
guard let fromId = FirebaseManager.shared.auth.currentUser?.uid else {return} //if there is current user's id store it in fromId
guard let toId = interlocutor?.uid else {return} //if there is interculor store their uid in toId
let document = FirebaseManager.shared.firestore.collection("messages") //create a message document |FOR SENDER| in message/fromId/toId/
.document(fromId)
.collection(toId)
.document()
let messageData = [ //create a message data variable
"fromId" : fromId,
"toId" : toId,
"text" : chatText,
"timestamp" : Timestamp()
] as [String : Any]
document.setData(messageData) { err in //for the document created set data using created messageData
if let err = err { //error handler
print("Failed to send a message: ", err)
return
}
self.persistRecentMessage()
self.chatText = "" //when the handleSend() is called clean chatText variable as so textFiled
self.count += 1 //increase message count (for scroll down)
}
let recepientMessageDocument = FirebaseManager.shared.firestore.collection("messages") //create a message document |FOR RECEPINT| in messages/toId/fromId/
.document(toId)
.collection(fromId)
.document()
recepientMessageDocument.setData(messageData) { err in //for the document created set data using created messageData
if let err = err { //don't have to create another message data because it is the same
print("Failed to send a message: ", err)
return
}
}
}
private func persistRecentMessage() {
guard let uid = FirebaseManager.shared.auth.currentUser?.uid else {return} //create uid if uid of current user is available
guard let toId = interlocutor?.uid else {return}
let document = FirebaseManager.shared.firestore.collection("recent_messages") //create document to store the message to /recent_messages/uid/messages/toId
.document(uid)
.collection("messages")
.document(toId)
let data = [ //recent message data variable
"timestamp" : Timestamp(),
"text" : self.chatText,
"formId" : uid,
"toId" : toId,
"profilePicURL": interlocutor?.profilePictureUrl ?? "",
"email" : interlocutor?.email ?? ""
] as [String : Any]
document.setData(data) { err in //set recent message to created document
if let err = err {
print("Faild to store recent message: ", err)
return
}
}
let recepientData = [ //recent message data variable
"timestamp" : Timestamp(),
"text" : self.chatText,
"formId" : uid,
"toId" : toId,
"profilePicURL": "",
"email" : ""
] as [String : Any]
let recepientRecentMessageDocument = FirebaseManager.shared.firestore
.collection("recent_messages") //create a recent message document |FOR RECEPINT| in /recent_messages/toId/messages/uid
.document(toId)
.collection("messages")
.document(uid)
recepientRecentMessageDocument.setData(data) { err in //for the document created set data using created messageData
if let err = err { //dont have to create another message data because it is the same
print("Failed to send a message: ", err)
return
}
}
}
}
struct MessagesView: View {
#EnvironmentObject var vm : MainMessagesViewModel
var body: some View{
ScrollView {
ForEach (vm.recentMessages) { recentMessage in
VStack {
NavigationLink {
Text("")
} label: {
HStack {
WebImage(url: URL(string:recentMessage.profilePictureUrl))
.resizable()
.scaledToFill()
.frame(width: 64, height: 64)
.clipShape(Circle())
.padding(.horizontal)
VStack (alignment: .leading) {
Text(recentMessage.email)
Text(recentMessage.text)
}
Spacer()
Text("date")
}
.padding(.horizontal)
Divider()
}
}
}
}
.navigationBarTitle(Text("მეგობრები"), displayMode: .inline)
}
}
I need to chat log view do not exit when updating contents in messages view.
When I commented out self.persistRecentMessage() from func handleSend() function in ChatLogViewModel, disabling in this way updating of contents in Messages View, ChatLogView stopped exiting

Swiftui Force Update View

In my content view I have a home page with some text that says "Welcome, xxxx" where xxxx is the name fetched from a firebase database. This field can be changed in the settings page that is navigated to via a Navigation Link. When the name is changed and saved the name on the home page only updates when you force shutdown the app. How do I force update the view when you press the back button from settings.
This is how I display the field:
Text("Welcome, \(companyName)")
.font(.system(size: 23))
.bold()
.foregroundColor(Color("background"))
.padding(.bottom, 50)
This is how I set a value to companyName:
func SetData() {
var db = Firestore.firestore()
let user = Auth.auth().currentUser
let userName = user?.email ?? ""
let docRef = db.collection("CONTACT").document(userName)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
//Setting Values
let data = document.data()
self.companyName = data?["companyName"] as? String ?? ""
} else {
print("Document does not exist")
}
}
}
There are several solutions to this, but you haven't provided enough code outlining what you have done to modify the variable companyName. The easiest solution would be to pass companyName as a binding value into the settings.
What I imagine here is that your HomeView is fetching the data on launch. In the settings, a change data request is made, but nothing is done to update the data in the HomeView. By using a binding variable we can ensure that the companyName connects to the source of truth in the HomeView, and so the function modifies the companyName which is precisely the company name on the HomeView vs. modifying potentially the value of companyName.
struct HomeView: View {
#State var companyName = "Microsoft"
var body: some View {
NavigationView {
NavigationLink(destination: SettingsView(companyName: $companyName)) {
Text("Tap to navigate to Settings")
}
}
}
}
struct SettingsView: View {
#Binding var companyName : String
var body: some View {
Button {
SetData()
} label: {
HStack {
Text("Tap to change!")
Text("\(companyName)!")
}
}
}
func SetData() {
var db = Firestore.firestore()
let user = Auth.auth().currentUser
let userName = user?.email ?? ""
let docRef = db.collection("CONTACT").document(userName)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
//Setting Values
let data = document.data()
self.companyName = data?["companyName"] as? String ?? ""
} else {
print("Document does not exist")
}
}
}
}
If you have already done this at it doesn't somehow work, another solution is to add an .onAppear modifier to your HomeView.
struct HomeView: View {
#State var companyName = "Microsoft"
var body: some View {
VStack {
// code ...
}
.onAppear {
fetchData()
}
}
func fetchData() {
// code that returns companyFetchedName
self.companyName = companyFetchedName
}
}
Modify it on main queue, like
docRef.getDocument { (document, error) in
if let document = document, document.exists {
//Setting Values
let data = document.data()
DispatchQueue.main.async { // << here !!
self.companyName = data?["companyName"] as? String ?? ""
}
} else {
print("Document does not exist")
}
}

Repeated messages in chatView. how to clear view?

I have a chatView with a list of chatRow Views (messages)
each chatView has a snapshot listener with firebase, so I should get real time updates if I add a new message to the conversation
The problem I have is: when I add a new message my chatView shows ALL the messages I added before plus the new message, PLUS the same list again....if I add another message then the list repeats again
I assume I need to drop/refresh the previous views shown in the Foreach loop...how can I drop/refresh the view so it can receive refreshed NON repeated data?
struct ChatView: View {
#EnvironmentObject var chatModel: ChatsViewModel
let chat: Conversation
let user = UserService.shared.user
#State var messagesSnapshot = [Message]()
#State var newMessageInput = ""
var body: some View {
NavigationView {
VStack {
ScrollViewReader { scrollView in
ScrollView {
ForEach(chat.messages, id: \.id) { message in
if user.name == message.createdBy {
ChatRow(message: message, isMe: true)
} else {
ChatRow(message: message, isMe: false)
}
}
.onAppear(perform: {scrollView.scrollTo(chat.messages.count-1)})
}
}
Spacer()
//send a new message
ZStack {
Rectangle()
.foregroundColor(.white)
RoundedRectangle(cornerRadius: 20)
.stroke(Color("LightGrayColor"), lineWidth: 2)
.padding()
HStack {
TextField("New message...", text: $newMessageInput, onCommit: {
print("Send Message")
})
.padding(30)
Button(action: {
chatModel.sendMessageChat(newMessageInput, in: chat, chatid: chat.id ?? "")
print("Send message.")
}) {
Image(systemName: "paperplane")
.imageScale(.large)
.padding(30)
}
}
}
.frame(height: 70)
}
.navigationTitle("Chat")
}
}
}
function to add message to the conversation
func addMessagesToConv(conversation: Conversation, index: Int) {
var mensajesTotal = [Message]()
let ref = self.db.collection("conversations").document(conversation.id!).collection("messages")
.order(by: "date")
.addSnapshotListener { querySnapshotmsg, error in
if error == nil {
//loop throug the messages/docs
for msgDoc in querySnapshotmsg!.documents {
var m = Message() //emtpy struc message
m.createdBy = msgDoc["created_by"] as? String ?? ""
m.date = msgDoc["date"] as? Timestamp ?? Timestamp()
m.msg = msgDoc["msg"] as? String ?? ""
m.id = msgDoc.documentID //firebase auto id
mensajesTotal.append(m) //append this message to the total of messages
self.chats[index].messages.removeAll()
self.chats[index].messages = mensajesTotal
}
} else {
print("error: \(error!.localizedDescription)")
}
}
}
You've defined mensajesTotal outside of your snapshot listener. So, it's getting appended to every time.
To fix this, move this line:
var mensajesTotal = [Message]()
to inside the addSnapshotListener closure.
You have two options:
Clear mensajesTotal each time you get an update from the database, as #jnpdx's answer shows.
Process the more granular updates in querySnapshotmsg.documentChanges to perform increment updates in your UI, as also shown in the documentation on detecting changes between snapshots.
There is no difference in the data transferred between client and server between these approaches, so use whatever is easiest (that'd typically be #1) or most efficient on the UI (that's usually #2).

Trying to show fetched firebase data on profile view SwiftUI

I'm trying to show the data I fetched from my Firebase database. I tried creating #State var variables and add them to my function but it didn't work. I tried printing my function output in a button to print it to console and it works. I just don't know how to show them in my view my code
import SwiftUI
import Firebase
struct ProfileView: View {
var body: some View {
VStack {
Button(action: {
profilef()
}) {
Text("hello")
}
HStack {
Button(action: {
try! Auth.auth().signOut()
UserDefaults.standard.set(false, forKey: "status")
NotificationCenter.default.post(name: NSNotification.Name("statusChange"), object: nil)
}) {
Text("Logout")
}
}
}
}
func profilef() {
let userID = Auth.auth().currentUser?.uid
let ref = Database.database().reference()
ref.child("UserInfo").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? [String : AnyObject]
let name = value?["fullName"] as? String ?? ""
print(name)
// ...
}) { error in
print(error.localizedDescription)
}
}
}
Just create a #State variable, which contains the name. If your function changes that variable, your view will updates.
struct profile: View {
#State var name : String = ""
var body: some View {
Text("Hello " + self.name)
And then in your function, instead of printing you will assign it to your state.
let name = value?["fullName"] as? String ?? ""
print(name)
self.name = name
That should work. I do not have an example with Firebase at the moment, so I can not test it. If it is not working, please describe the behavior.
Adding an #State property profileName and assigning it in the network request function will work after tapping the Button.
// ProfileView.swift
//
//
// Created by Shahin Bararesh on 2020-09-07.
//
import SwiftUI
import Firebase
struct ProfileView: View {
#State var profileName: String = ""
var body: some View {
VStack {
Button(action: {
profilef()
}) {
Text(profileName)
}
HStack {
Button(action: {
try! Auth.auth().signOut()
UserDefaults.standard.set(false, forKey: "status")
NotificationCenter.default.post(name: NSNotification.Name("statusChange"), object: nil)
}) {
Text("Logout")
}
}
}
}
func profilef() {
let userID = Auth.auth().currentUser?.uid
let ref = Database.database().reference()
ref.child("UserInfo").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? [String : AnyObject]
let name = value?["fullName"] as? String ?? ""
self.profileName = name
// ...
}) { error in
print(error.localizedDescription)
}
}
}

Is there a way to give initial value in an instance in property initializer dynamically using SwiftUI and Firebase?

Hi I am currently making a dating app's chat page that you can have different rooms for every match using SwiftUI and Cloud Firestore.
I would like to show different chat room every time you tap different user on the top page depending on the matchId.
For now, I need to type the right one in the View file in order to make it work correctly, however, Id like to assign it dynamically.
How can I add the correct matchId to the instance in the View file? Or, should I try different ways?
First, this is the top page.
VStack{
Text("Match Users")
List(self.shareData.matchUserArray){ user in
NavigationLink(destination: MessageView(matchUserInfo: user)){
HStack{
Text(user.name)
Text(user.age)
}
}
}
}
And this is the View file. Without typing "Ll73RINefGxEcYQJoWSE" in the MessageViewModel instance and instead giving it "", I can see the messages in the debug area but don't see any in List.
struct MessageView: View {
var matchUserInfo: User
#ObservedObject var msgVM = MessageViewModel(matchId: "Ll73RINefGxEcYQJoWSE")
#EnvironmentObject var shareData : ShareData
#State var text = ""
#State var matchId = ""
var body: some View {
VStack{
List(self.msgVM.messages, id: \.id){ i in
if i.fromUser == self.shareData.currentUserData["id"] as? String ?? ""
{
MessageRow(message: i.msg, isMyMessage: true)
} else if i.toUser == self.shareData.currentUserData["id"] as? String ?? ""
{
MessageRow(message: i.msg, isMyMessage: false)
}
}
.onAppear { UITableView.appearance().separatorStyle = .none }
.onDisappear { UITableView.appearance().separatorStyle = .singleLine }
HStack{
TextField("message here", text: $text).textFieldStyle(RoundedBorderTextFieldStyle()).padding()
Button(action: {
if self.text.count > 0 {
self.msgVM.sendMsg(msg: self.text, toUser: self.matchUserInfo.id, fromUser: self.shareData.currentUserData["id"] as! String, matchId: self.msgVM.matchId)
self.text = ""
}
}) {
Image(systemName: "paperplane")
}.padding(.trailing)
}
}
.navigationBarTitle("\(self.matchUserInfo.name)", displayMode: .inline)
.onAppear{
DispatchQueue.global().async{
self.getMatchId(partner: self.matchUserInfo)
}
_ = MessageViewModel(matchId: self.matchId)
}
.onDisappear{
print(self.msgVM.messages)
}
}
func getMatchId(partner: User){
Firestore.firestore().collection("MatchTable").document(self.shareData.currentUserData["id"] as? String ?? "").collection("MatchUser").whereField("MatchUserId", isEqualTo: partner.id).getDocuments { (snap, err) in
if let snap = snap {
for id in snap.documents{
self.msgVM.matchId = id.data()["MatchRoomId"] as? String ?? ""
_ = MessageViewModel(matchId: self.msgVM.matchId)
self.matchId = self.msgVM.matchId
}
}
}
}
}
Also this is the firebase part.
import Foundation
import FirebaseFirestore
struct Message: Identifiable {
var id: String
var msg: String
var fromUser: String
var toUser: String
var date: Timestamp
var matchId : String
}
class MessageViewModel: ObservableObject {
var datas = FirebaseData()
let db = Firestore.firestore()
#Published var matchId:String
#Published var messages = [Message]()
init(matchId: String){
self.matchId = matchId
self.db.collection("Messages").whereField("matchId", isEqualTo: self.matchId).order(by: "date").addSnapshotListener { (snap, error) in
if let error = error {
print(error.localizedDescription)
return
}
if let snap = snap {
for i in snap.documentChanges {
if i.type == .added{
let toUser = i.document.get("toUser") as! String
let fromUser = i.document.get("fromUser") as! String
let message = i.document.get("message") as! String
let id = i.document.documentID
let date = i.document.get("date") as! Timestamp
let matchId = i.document.get("matchId") as! String
self.messages.append(Message(id: id, msg: message, fromUser: fromUser, toUser: toUser, date: date, matchId: matchId))
}
}
}
}
}
func sendMsg(msg: String, toUser: String, fromUser: String, matchId: String){
let data = [
"message": msg,
"toUser": toUser,
"fromUser": fromUser,
"date": Timestamp(),
"matchId": matchId
] as [String : Any]
Firestore.firestore().collection("Messages").addDocument(data: data){ error in
if let err = error {
print(err.localizedDescription)
return
}
print("Sent message")
}
}
}
Thank you
All you should really need is to construct your ObservedObject in an init function:
let matchUserInfo: User
#ObservedObject private var msgVM: MessageViewModel
init(_ user: User) {
self.matchUserInfo = user
self._msgVM = ObservedObject(initialValue: MessageViewModel(matchId: user.matchId))
}
Assuming, of course, that the matchId you care about is passed in via your User type. You know your data structures better than I do, the key here is to simply create your observed object based on your passed in User.

Resources