This code adds all the data in a single array. In HomeViev I use to Foreach and I added to data to list. But I have to split the data in two. status collection has two type "active" and "closed" but I don't know how can I filter
import SwiftUI
import Combine
import Firebase
let dbCollection = Firestore.firestore().collection("Signals")
class FirebaseSession : ObservableObject {
#Published var session: User? { didSet { self.didChange.send(self) }}
#Published var data = [Signal]()
var didChange = PassthroughSubject<FirebaseSession, Never>()
var handle: AuthStateDidChangeListenerHandle?
func listen () {
handle = Auth.auth().addStateDidChangeListener { (auth, user) in
if let user = user {
print("Got user: \(user)")
self.session = User(uid: user.uid, email: user.email)
self.readData()
} else {
self.session = nil
}
}
}
func readData() {
dbCollection.addSnapshotListener { (documentSnapshot, err) in
if err != nil {
print((err?.localizedDescription)!)
return
}else {
print("read data success")
}
documentSnapshot!.documentChanges.forEach { i in
// Read real time created data from server
if i.type == .added {
let id = i.document.documentID
let symbol = i.document.get("symbol") as? String ?? ""
let status = i.document.get("status") as? String ?? ""
self.data.append(Signal(id: id, symbol: symbol, status: status))
}
// Read real time modify data from server
if i.type == .modified {
self.data = self.data.map { (eachData) -> Signal in
var data = eachData
if data.id == i.document.documentID {
data.symbol = i.document.get("symbol") as! String
data.status = i.document.get("status") as? String ?? ""
return data
}else {
return eachData
}
}
}
// When data is removed...
if i.type == .removed {
let id = i.document.documentID
for i in 0..<self.data.count{
if self.data[i].id == id{
self.data.remove(at: i)
return
}
}
}
}
}
}
}
The question states
But I have to split the data in two
I assume that means two arrays; one for active and one for closed.
var activeData = [...
var closedData = [...
There are a couple of ways to do that
1)
Query Firestore for all status fields equal to active and load those documents into the active array and then another query for status fields equal closed and load those in the the closed array
2)
I would suggest a simpler approach
if i.type == .added {
let id = i.document.documentID
let symbol = i.document.get("symbol") as? String ?? ""
let status = i.document.get("status") as? String ?? ""
if status == "active" {
self.activeData.append(Signal(id: id, symbol: symbol, status: status))
} else {
self.closedData.append(Signal(id: id, symbol: symbol, status: status))
}
}
and do the same thing within .modified and .removed; identify the status so the code will know which array to remove it from.
EDIT:
Based on a comment
I don't know how to query this codes.
I am providing code to query for signals that are active. This code will return only active signals and as signals become active, inactive etc, this will modify a signalArray to stay in sync with the data.
let dbCollection = Firestore.firestore().collection("Signals")
let query = dbCollection.whereField("status", isEqualTo: "active").addSnapshotListener( { querySnapshot, error in
guard let snapshot = querySnapshot else {
print("Error fetching snapshots: \(error!)")
return
}
snapshot.documentChanges.forEach { diff in
if (diff.type == .added) {
let signalToAdd = Signal(withDoc: diff.document)
self.signalArray.append(signalToAdd)
}
if (diff.type == .modified) {
let docId = diff.document.documentID
if let indexOfSignalToModify = self.signalArray.firstIndex(where: { $0.signal_id == docId} ) {
let signalToModify = self.signalArray[indexOfSignalToModify]
signalToModify.updateProperties(withDoc: diff.document)
}
}
if (diff.type == .removed) {
let docId = diff.document.documentID
if let indexOfSignalToRemove = self.signalArray.firstIndex(where: { $0.signal_id == docId} ) {
self.signalArray.remove(at: indexOfSignalToRemove)
}
}
}
})
Note that my Signal Class has an initializer that accepts a QueryDocumentSnapshot to initialize it as well as a .updateProperties function to update its internal properties.
Related
I made a code that adds likes and shows their number on the screen.
But there is a problem, when you download the application on 2 devices and press the button at the same time, then only one like is counted. How can I fix this without implementing registration?
There is an idea to make fields that will be created for everyone on the phone when the like is pressed and this number will be added to the total, but I do not know how to implement this.
Here's the current code:
struct LikeCounts {
var likecount: String
}
class LikeTextModel: ObservableObject {
#Published var likecounts: LikeCounts!
private var db = Firestore.firestore()
init() {
updateLike()
}
func updateLike() {
db.collection("likes").document("LikeCounter")
.addSnapshotListener { documentSnapshot, error in
guard let document = documentSnapshot else {
print("Error fetching document: \(error!)")
return
}
guard let data = document.data() else {
print("Document data was empty.")
return
}
if let likecount = data["likecount"] as? String {
DispatchQueue.main.async {
self.likecounts = LikeCounts(likecount: likecount)
}
}
}
}
#ObservedObject private var likeModel = LikeTextModel()
if self.likeModel.likecounts != nil{
Button(action:
{self.like.toggle()
like ? addlike(): dellike()
UserDefaults.standard.setValue(self.like, forKey: "like")
}) {
Text((Text(self.likeModel.likecounts.likecount))}
func addlike() {
let db = Firestore.firestore()
let like = Int.init(self.likeModel.likecounts.likecount)
db.collection("likes").document("LikeCounter").updateData(["likecount": "\(like! + 1)"]) { (err) in
if err != nil {
print(err)
return
}
}
}
func dellike() {
let db = Firestore.firestore()
let like = Int.init(self.likeModel.likecounts.likecount)
db.collection("likes").document("LikeCounter").updateData(["likecount": "\(like! - 1)"]) { (err) in
if err != nil {
print(err)
return
}
}
}
Firestore has the ability to reliably increment a value, like this:
db.collection('likes').doc('LikeCounter')
.updateData([
"likecount": FieldValue.increment(1)
]);
I have this way of collecting information.
struct MainText {
var mtext: String
var memoji: String
}
class MainTextModel: ObservableObject {
#Published var maintext : MainText!
init() {
updateData()
}
func updateData() {
let db = Firestore.firestore()
db.collection("maintext").document("Main").getDocument { (snap, err) in
if err != nil{
print((err?.localizedDescription)!)
return
}
let memoji = snap?.get("memoji") as! String
let mtext = snap?.get("mtext") as! String
DispatchQueue.main.async {
self.maintext = MainText(mtext: mtext, memoji: memoji)
}
}
}
}
And such a way of displaying.
#ObservedObject private var viewModel = MainTextModel()
self.viewModel.maintext.memoji
self.viewModel.maintext.mtext
How can I update online without rebooting the view?
Instead of using getDocument, which only gets the document once and doesn't return updates, you'll want to add a snapshot listener.
Here's the Firestore documentation for that: https://firebase.google.com/docs/firestore/query-data/listen
In your case, you'll want to do something like:
db.collection("maintext").document("Main")
.addSnapshotListener { documentSnapshot, error in
guard let document = documentSnapshot else {
print("Error fetching document: \(error!)")
return
}
guard let data = document.data() else {
print("Document data was empty.")
return
}
if let memoji = data["memoji"] as? String, let mtext = data["mtext"] as? String {
self.maintext = MainText(mtext: mtext, memoji: memoji)
}
}
Can you help me understand why this code is misbehaving? I have a List in a SwiftUI View that is populated by this function. It is an array called 'players'. It loads and the list looks great in the UI. However, when I manually edit a document in firestore, a new line item appears with the edits rather than the edits being applied in realtime to the existing item.
Hope that makes sense.
Here is the code -
func getBuddies(arr: [String],completion:#escaping(([String]) -> ())) {
var u: UserProfile = UserProfile(id: "", name: "", nickname: "", loginID: "", email: "", createdBy: "")
//var newArr : [String] = []
let g = DispatchGroup()
for id in arr {
g.enter()
store.collection(path)
.whereField(FieldPath.documentID(), isEqualTo: id)
.addSnapshotListener { querySnapshot, error in
if let error = error {
print("Error getting > courses: \(error.localizedDescription)")
return
}
for document in querySnapshot!.documents {
u.id = document.documentID
u.name = document.get("name") as! String
u.nickname = document.get("nickname") as! String
u.email = document.get("email") as! String
u.createdBy = document.get("createdBy") as! String
u.loginID = document.get("loginID") as! String
self.players.append(contentsOf: querySnapshot!.documents.compactMap {(queryDocumentSnapshot) -> UserProfile? in
return try? queryDocumentSnapshot.data(as: UserProfile.self)})
g.leave()
}
}
}
g.notify(queue:.main) { [self] in
print("NEW ARRAY: \(players)")
completion(arr)
}
}
UPDATE::
I have also tried to process the callback according to the transaction type - the following works fine for adds but I'm not sure how to handle changes and deletes.
for document in querySnapshot!.documents {
u.id = document.documentID
u.name = document.get("name") as! String
u.nickname = document.get("nickname") as! String
u.email = document.get("email") as! String
u.createdBy = document.get("createdBy") as! String
u.loginID = document.get("loginID") as! String
querySnapshot!.documentChanges.forEach { diff in
if (diff.type == .added) {
self.players.append(contentsOf: querySnapshot!.documents.compactMap {(queryDocumentSnapshot) -> UserProfile? in
return try? queryDocumentSnapshot.data(as: UserProfile.self)})
print("New player: \(diff.document.data())")
}
if (diff.type == .modified) {
print("Modified city: \(diff.document.data())")
}
if (diff.type == .removed) {
print("Removed city: \(diff.document.data())")
}
}
g.leave()
}
I'm new to Swift and I'm having trouble sending data in my Firestore database to my global data structure (array in a struct). I looked through many similar questions and tried their solutions for hours to no avail, so please help me. I am totally stuck.
I somewhat understand that the problem lies in the initializeDocs() section of my code being asynchronous. I've tried DispatchQueue.main.async as well as completion handlers (although I'm not sure if the way I did them was correct), but I'm still getting the same result- i.e. the Firestore data is not being saved in the struct and I am instead getting back an empty array when I try calling them in my init function. Any help is appreciated.
Here is my code:
struct booksStruc{
static var modelAry = [Model]()
static var titles: [String] = []
static var authors: [String] = []
static var years: [String] = []
static var emails: [String] = []
static var capacity = 0
static var counter = 0
}
init(logo:String, title:String, author:String, year:String, email:String, desc:String) {
let group = DispatchGroup()
ref = Database.database().reference()
super.init()
group.enter()
DispatchQueue.main.async{
self.initializeDocs()
group.leave()
}
self.logoTitle = logo
self.imageTitle = title
self.imageAuthor = author
self.imageYear = year
self.imageEmail = email
self.imageDesc = desc
print("end of init's titles are ", booksStruc.titles)
print("end of init's capacity is ", booksStruc.capacity)
}
func initializeDocs() {
db.collection("books").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
let downloadGroup = DispatchGroup()
downloadGroup.enter()
DispatchQueue.main.async{
for document in querySnapshot!.documents {
let dictionary = document.data()
booksStruc.capacity = document.data().capacity
//print("capacity is: ", capacity)
for(key, value) in dictionary{
if(key == "title"){
booksStruc.titles.append(value as! String)
} else if(key == "author"){
booksStruc.authors.append(value as! String)
} else if(key == "year"){
booksStruc.years.append(value as! String)
} else if(key == "email"){
booksStruc.emails.append(value as! String)
}
//print("\(key) : \(value)")
}
}
downloadGroup.leave()
}
}
}
print("titles are ", booksStruc.titles)
print("capacity is ", booksStruc.capacity)
}
I understand that Firestore loads data asynchronously, but I want to use these data later and in different ViewControllers. Is there any possibility, to save data in array?
func findPlayers (filters: Dictionary<String, Any>) -> [String] {
let reference = dataService.instance.dbF.collection("playersStats")
var query1: Query
var keysArray = [String?] ()
var resultIDs = [String] ()
for key in filters.keys {
if key != "PositionName" {
keysArray.append(key)
}
}
if filters.keys.count == 1 {
if keysArray[0] != nil {
let value = filters[keysArray[0]!] as? Double
query1 = reference.whereField(keysArray[0]!, isGreaterThan: value! )
query1.getDocuments{ (snapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in snapshot!.documents {
let id = String(document.documentID)
resultIDs.append(id)
}
}
}
}
}
return resultIDs
}
I really expect to see array full of data I want to get.