How to Initialize Firestore Document Reference Swift - ios

I have a user model as per the below which gives me a dictionary of a User.
import UIKit
import FirebaseFirestore
protocol SerializeUser {
init?(dictionary: [String: Any])
}
struct User {
var documentRef: DocumentReference?
var displayName: String
var photoURL: String
var email: String
var isEmployee: Bool
var dictionary: [String: Any] {
return ["displayName": displayName, "photoURL": photoURL, "email": email, "isEmployee": isEmployee]
}
}
extension User: SerializeUser {
init?(dictionary: [String: Any]) {
guard
let displayName = dictionary["displayName"] as? String,
let photoURL = dictionary["photoURL"] as? String,
let isEmployee = dictionary["isEmployee"] as? Bool,
let email = dictionary["email"] as? String else { return nil }
self.init(displayName: displayName, photoURL: photoURL, email: email, isEmployee: isEmployee)
}
}
I need to initialize the documentRef within my User struct somehow any ideas on how to do that? please.

Somewhere you need an instance of your database to build the document reference.
var db: Firestore? = Firestore.firestore()
struct User {
lazy var documentRef: db.collection("users").document(displayName)
}
I don't see a document id referenced anywhere here so I assumed you're using displayName as a key. Declaring the variable as lazy lets you initialize it with your other instance variables.
Hope this helps.

I have solved this by using Decodable Protocol as illustrated in this post;
Using Decodable to get the object and document ID with Firestore

Related

Swift Firebase Processing A Custom Object

I am trying to store a struct called 'UnlockingCharacters' in the users document on firebase. I have a struct called 'Character'. When a user taps "unlock" on a character, the 'Character' is added to 'UnlockingCharacters'. I need to store this on firebase in the users document but am struggling to do this.
I have managed to add a 'Character' to 'UnlockingCharacters' and display them in the users profile however it is not stored in firebase so when the app is closed, the 'Character' is no longer in 'UnlockingCharacters'
Here are my structs & classes:
struct Character: Identifiable, Codable {
#DocumentID var id: String?
var character_name: String
var character_type: String
var character_image: String
var character_details: String
var character_usersUnlocking: Int
var character_totalPoints: Int
var user: UserModel?
var didUnlock: Bool? = false
// To identify whether it is being unlocked...
var isUnlocking: Bool = false
}
struct UnlockingCharacters: Identifiable, Codable {
var id = UUID().uuidString
var character: Character
}
class SharedDataModel: ObservableObject {
// Unlocking Characters...
#Published var unlockingCharacters: [Character] = []
}
My functions:
func isUnlocked() -> Bool {
return sharedData.unlockingCharacters.contains { characterData in
return self.characterData.id == characterData.id
}
}
func addToUnlocking() {
if let index = sharedData.unlockingCharacters.firstIndex(where: {
characterData in
return self.characterData.id == characterData.id
}){
// Remove from unlocking...
sharedData.unlockingCharacters.remove(at: index)
}
else {
// Add to unlocking...
sharedData.unlockingCharacters.append(characterData)
}
}
And my UserModel:
struct UserModel: Identifiable, Codable {
var username : String
var pic : String
var bio: String
var uid : String
var id: String { uid }
var activeUnlockingCharacters: [UnlockingCharacters]
}
When trying to process the custom object I get errors:
let ref = Firestore.firestore()
func fetchUser(uid: String,completion: #escaping (UserModel) -> ()){
let db = Firestore.firestore()
ref.collection("Users").document(uid).getDocument { (doc, err) in
guard let user = doc else{return}
let username = user.data()?["username"] as? String ?? "No Username"
let pic = user.data()?["imageurl"] as? String ?? "No image URL"
let bio = user.data()?["bio"] as? String ?? "No bio"
let uid = user.data()?["uid"] as? String ?? ""
do {
try db.collection("Users").document("\(uid)").setData(from: UnlockingCharacters)
} catch let error {
print("Error writing object to Firestore: \(error)")
}
DispatchQueue.main.async {
completion(UserModel(username: username, pic: pic, bio: bio, uid: uid, activeUnlockingCharacters: UnlockingCharacters))
}
}
}
I also get errors in the following line inside my ProfileViewModel:
#Published var userInfo = UserModel(username: "", pic: "", bio: "", uid: "", activeSupportingCharities: [SupportingCharities])
The errors:
Missing argument for parameter 'activeUnlockingCharacters' in call
Cannot convert value of type '[UnlockingCharacters].Type' to expected argument type '[UnlockingCharacters]'
Here is my data structure in the firebase console:
I want there to be a field called UnlockingCharacters in the users data model on firebase when a character is added to the UnlockingCharacters struct.
I think the issue is that your code for writing back to the User document doesn't refer to an instance of UnlockingCharacters , but instead to the type UnlockingCharacters.
So this line:
try db.collection("Users").document("\(uid)").setData(from: UnlockingCharacters)
should probably(*) become
let userModel = UserModel(username: username, pic: pic, bio: bio, uid: uid, activeUnlockingCharacters: unlockedCharacters)
try db.collection("Users").document("\(uid)").setData(from: userModel)
*: probably, because I wasn't sure about your data structure. You might want to post a screenshot of your Firestore data model (in the console) to make it easier to understand how you're intending to store this data.
Also, two other notes:
You probably want to use Codable to replace the manual mapping (let username = user.data()?["username"] as? String ?? "No Username" etc.)
no need to wrap the UI update in DispatchQueue.main.async - Firestore calls back on the main thread already - see https://twitter.com/peterfriese/status/1489683949014196226 .

Splitting up an array and assigning values to each value in it

Im using Firestore and when I request data, it comes in an array
print("Document data: \(dataDescription)")
which prints out
Document data: ["lastname": Test, "firstname": Test]
I created a class
class User {
var firstName: String
var lastName: String
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
}
How can I assign the part of the array for "lastname" to the User class and assign in to lastName
A better way to do these parsings of Json to Model and vice versa is by using any parsing library e.g. ObjectMapper, SwiftyJson or swift's Codable protocol. You can do both way parsing with such an ease with them and even for complex data model you don't have to do a lot of work.
So better not to reinvent the wheel.
Here are some examples for your specific usecase.
let userData = ["lastname": "Last", "firstname": "First"]
Using ObjectMapper: (Install pod and add the import to get it work)
struct User: Mappable {
var firstname: String?
var lastname: String?
init?(map: Map) {}
mutating func mapping(map: Map) {
firstname <- map["firstname"]
lastname <- map["lastname"]
}
}
if let newuser = Mapper<User>().map(JSON: userData) {
print(newuser)
}
Using Codeable protocol:
struct User: Codable {
var firstname: String
var lastname: String
}
do {
let data = try JSONSerialization.data(withJSONObject: userData, options: .prettyPrinted)
let decoder = JSONDecoder()
let user = try decoder.decode(User.self, from: data)
} catch {
print(error.localizedDescription)
}
This
["lastname": Test, "firstname": Test]
is a dictionary not an array , you need
guard let res = snap.value as? [String:String] ,let fname = res["firstName"] ,
let lname = res["lastName"] else { return }
let user = User(firstName:fname,lastName:lname)
Tip:think if you really need a struct not class

How to rectify the error of "self' used before 'self.init' call or assignment to 'self'"?

I am facing the error of self used before self.init call or assignment to self in the below code for the model class for the tableview cell item, it happened after I tried to get document id of the table cell item.
What should be done? Please recommend.
import Foundation
import Firebase
import FirebaseFirestore
protocol DocumentSerializable {
init?(dictionary:[String:Any])
}
struct Post {
var _postKey: String!
var _username: String!
var _postTitle: String!
var _postcategory: String!
var _postContent: String!
var dictionary:[String:Any] {
return [
"username": _username,
"postTitle":_postTitle,
"postcategory":_postcategory,
"postContent":_postContent,
"postKey":_postKey
]
}
}
extension Post : DocumentSerializable {
init?(dictionary: [String : Any]) {
guard let postKey = key,
let username = dictionary["username"] as? String,
let postTitle = dictionary["postTitle"] as? String,
let postcategory = dictionary["postcategory"] as? String,
let postContent = dictionary["postContent"] as? String else { return nil }
self.init(_postKey: postKey, _username: username ,_postTitle: postTitle, _postcategory: postcategory, _postContent: postContent)
}
}
You have some circular dependency going on, to access the dictionary variable you need an instance, for the instance to be created you need to call init but again to call init you should have the dictionary variable initialized and so on. You could make the dictionary variable static.

How add data to a map

I use google cloud firestore and I have a map in the documents. I then want to push an updated map to the database.
The document looks like this:
My model for the document looks like this:
import Foundation
import Firestore
protocol DocumentSerializable {
init?(dictionary:[String:Any])
}
struct Exercise {
var title: String
var language: String
var translated: String
var uid: String
var userId: String
var dueDate: Int
var lastOpen: Int
var words: Array<Any>
var sharedUsers: [String: Bool]
var dictionary:[String:Any] {
return [
"title":title,
"language":language,
"translated":translated,
"uid":uid,
"userId":userId,
"dueDate":dueDate,
"lastOpen":lastOpen,
"words":words,
"sharedUsers":sharedUsers
]
}
}
extension Exercise : DocumentSerializable {
init?(dictionary: [String : Any]) {
guard let title = dictionary["title"] as? String,
let language = dictionary["language"] as? String,
let translated = dictionary["translated"] as? String,
let uid = dictionary["uid"] as? String,
let userId = dictionary["userId"] as? String,
let dueDate = dictionary["dueDate"] as? Int,
let lastOpen = dictionary["lastOpen"] as? Int,
let words = dictionary["words"] as? Array<Any>,
let sharedUsers = dictionary["sharedUsers"] as? [String: Bool]
else {return nil}
self.init(title: title, language: language, translated: translated, uid: uid, userId: userId, dueDate: dueDate, lastOpen: lastOpen, words: words, sharedUsers: sharedUsers)
}
}
So, how can I create an object that looks like this, (String:Bool), and put that in a map?
I hope I have provided enough information, otherwise feel free to ask.
Thanks in advance!

Can't create an object with JSON response from API call

I have made an API call to log my users in an app. i am using Alamofire and SwiftyJSON. API call works well and I get a JSON file like this one :
{
"code": 200,
"message": "OK",
"data": {
"user_id": 1,
"user_email": "test",
"user_username": "kk1",
}
}
In order to save the user info in UserDefault, I want to create my User object with this JSON response. Here is the model
class LFUser: NSObject {
var user_id: Int?
var user_email: String?
var user_username: String?
init(dict: [String: AnyObject]){
super.init()
user_id = dict["user_id"] as? Int
user_email = dict["user_email"] as? String
user_username = dict["user_username"] as? String
}
}
Here is the part of the login function when the API call and the objectis created :
func login(userEmail: String, userPassword: String, finished: #escaping(_ status:String,_ data: LFUser?)-> ()) {
if let value = response.result.value {
let dict = JSON(value)
let code = dict["code"].intValue
let message = dict["message"].stringValue
if let data = dict["data"].dictionary {
print(data)
let user = LFUser(dict: data as [String : AnyObject])
print(user.user_email)
finished("Success", user)
}
}
The print(data) works well but there is a problem when creating the object and the print(user.user_email) display nil.
Please check this :
if let data = dict["data"].dictionary {
let user = LFUser(dict: data)
print(user.user_email)
finished("Success", user)
}
class LFUser: NSObject {
var user_id: Int?
var user_email: String?
var user_username: String?
init(dict: [String : SwiftyJSON.JSON]){
super.init()
user_id = dict["user_id"]?.intValue
user_email = dict["user_email"]?.stringValue
user_username = dict["user_username"]?.stringValue
}
}
I would go for Swift 4’s Codable instead:
struct User: Codable {
let user_id: Int
let user_email: String
let user_username: String
}
struct Response: Codable {
let code: Int
let message: String
let data: User
}
Then you can decode the incoming data like that:
let response = try JSONDecoder().decode(Response.self, from: jsonData)
Then, in order to save the value to user defaults, you can use JSONEncoder or PropertyListEncoder to encode the value to Data.

Resources