Newbie SwiftUI Nested array - mvvm

Please help me display my model data from api
I have a nested json data response and Have a swiftui model as below
****************************MyData model
struct MyData: Codable {
let data: [Account]
}
// MARK: - Datum
struct Account: Codable {
let id, username, password: String
}
my previous model was just the array but I adjusted for Nest. I read data and end up with read success from api below
` success(SwiftClient.MyData(data: [SwiftClient.Account(id: "1", username: "ooo111", password: "$2a$10$tyFnx6.7yR/MY3oQHDlySOoooG9RIpusOEIGmDCRkOI9ZXzV3rkpy"), SwiftClient.Account(id: "2", username: "oo222", password: "$2a$10$3iR3SdEjkVZ5w7/lgTdZwOvooohqd1L0jDt30k/nmSt0h47VyLfe")]))
********************** ```
however when I want to use my model to present data it fails
My viewmodel is below
` struct AccountViewModel {
let account: MyData
var id: String {
// return account.name
return account.data.first!.id
}
var username: String {
//return account.name
return account.data.first!.username
}
var password: String {
//return account.balance
return account.data.first!.password
}
} ```
When I call it in below it runs but is empty array
\\\\and in my function call to get Accounts
case .success(let accounts):
DispatchQueue.main.async {
self.accounts = accounts.data.map(AccountViewModel.init)
} ```
***********
self.accounts = accounts.data.map... - fails with Value of type '[AccountViewModel]' has no member 'data'
what's the best way to unwrap my nest and display accounts
Thanks In advance for help

I was using my struct outer nest when reading data . I just had to change they mydata back to account ion my model and it worked.
struct AccountViewModel {
//let account: MyData
let account: Account
var id: String {
return account.id
}
var username: String {
return account.username
}
var password: String {
return account.password
}
}

Related

How can I fetch a json file using Vapor for my leaf template to show the data?

I have a JSON hosted somewhere and I want to fetch the content, put it in a context for my leaf template to read.
However, I cannot make it work. I get the code to compile, but I get an error in the localhost
{"error":true,"reason":"Unsupported Media Type"}
Can somebody help me please! Happy holidays for all.
struct WebsiteController: RouteCollection {
func boot(routes: RoutesBuilder) throws {
routes.get(use: indexHandler)
}
func indexHandler(_ req: Request) -> EventLoopFuture<View> {
return req.client.get("https://streeteasydaily.s3.us-west-2.amazonaws.com/streeteasy1.json").flatMap { res in
do {
let json = try res.content.decode([Listing].self)
print(json[0].photos[0])
let context = IndexContext(title: "Homepage", listings: json)
return try req.view.render("index", context)
} catch {
// Handle error
print("cayo en error")
return req.eventLoop.makeFailedFuture(error)
}
}
}
}
struct IndexContext: Encodable {
let title: String
let listings: [Listing]
}
Model
final class Listing: Model {
static let schema = "listings" //basically the table name
#ID
var id: UUID?
#Field(key: "address")
var address: String
#Field(key: "description")
var description: String
#Field(key: "photos")
var photos: [String]
init() {}
//to initialize the db
init(id: UUID? = nil, address: String, description: String, photos: [String]) {
self.id = id
self.address = address
self.description = description
self.photos = photos
}
}
//to make acronym conform to CONTENT, and use it in Vapor
extension Listing: Content {}
This error is because the decode is failing to identify all the fields in your JSON to match against those defined in Listing and/or the array of such objects. The filenames must match those in the JSON exactly - i.e. case-sensitive and every field in the structure/model must exist in the JSON. Additional fields in the JSON that are not needed/included in the structure/model are fine.

SwiftUI: compiler takes initialized value from ViewModel rather than the value that has been fetched

I'm writing a program where I reference a database where authenticated users each have a document whose ID corresponds to their User ID. Given the user's ID, I am trying to determine their name; I have managed to read all of the user's data and it is in my data model of class Users:
class Users {
var id: String
var name: String
var surname: String // ...
}
In my ViewModel, I have
#Published var specificUser = User(id: "", name: "", surname: "", email: "", profficiency: 0, lists: [[]])
which is an initialized user.
In that same ViewModel, I have a function that fetches the User Data from the database, which appears to work. It should then store the new user data in the specificUserData variable.
func getData() {
let db = Firestore.firestore()
guard let uid = auth.currentUser?.uid else { return }
db.collection("Users").getDocuments { result, error in
if error == nil {
print("Current User's ID found: \(uid)")
if let result = result {
// iterate through documents until correct ID is found
for d in result.documents {
if d.documentID == uid {
print("Document ID found: \(d.documentID)")
self.specificUser = User(
id: d.documentID,
name: d["name"] as? String ?? "",
// ...
)
print(self.specificUser)
print(self.specificUser.name) // This works; my compiler spits out the correct name from the database, so clearly the specificUser variable has been changed.
}
}
}
} else {
// Handle Error
print("Error while fetching user's specific data")
}
}
}
Here's how I initialized the getData() function:
init() {
model.getData()
print("Data Retrieval Complete")
print("User's Name: \(model.specificUser.name)")
}
I am trying to reference my ViewModel like this:
#ObservedObject var model = ViewModel()
Now here's the problem: when I try to reference the User's name from the view model in my struct with
model.specificUser.name
It gives me the default name, even though I have initialized the getData() function already. Checking my compiler log and adding a bunch of print statements, it appears that the initialization is in fact working, but it is printing data retrieval complete before it is printing the albeit correct name.
Any thoughts? It seems that the initializer function is taking the initialized value from my ViewModel rather than the correct value it should be computing.
try this
func getData(_ completion: #escaping (Bool, User?) -> ()) {
let db = Firestore.firestore()
guard let uid = auth.currentUser?.uid else { return }
db.collection("Users").getDocuments { result, error in
if error == nil {
print("Current User's ID found: \(uid)")
if let result = result {
// iterate through documents until correct ID is found
for d in result.documents {
if d.documentID == uid {
print("Document ID found: \(d.documentID)")
let user = User(
id: d.documentID,
name: d["name"] as? String ?? "",
// ...
)
completion(true, user)
print(self.specificUser)
print(self.specificUser.name) // This works; my compiler spits out the correct name from the database, so clearly the specificUser variable has been changed.
}
}
}
} else {
// Handle Error
completion(false, nil)
print("Error while fetching user's specific data")
}
}
}
init() {
model.getData() { res, user in
if res {
self.specificUser = user!
}
print("Data Retrieval Complete")
print("User's Name: \(model.specificUser.name)")
}
}

Vapor 4: Children relation not eager loaded, use $ prefix to acces

Currently I am working on a school assignment where we have to build an API using Vapor. I have a few basic API calls working and I am trying a bit more advanced API calls now but I can't get this to work.
I have this function addToParty that is being called when the URL /party/join/:partyID is called with a body
{
"id": "CC1FAC6B-A2B3-471C-A488-147300196981",
"username": "string",
"is_ready": true
}
I am trying to find a party by the partyId and add the user to the list of users of the party.
func addToParty (req: Request) throws -> EventLoopFuture<Party.Output> {
guard let id = req.parameters.get("partyID", as: UUID.self) else {
throw Abort(.badRequest)
}
let input = try req.content.decode(Party.JoinParty.self)
return Party.find(id, on: req.db)
.unwrap(or: Abort(.notFound))
.flatMap { element in
element.users.append(User(id: UUID(input.id), username: input.username, is_ready: input.is_ready))
return element.save(on: req.db)
.map{ Party.Output(code: "200") }
}
}
When I try the code above I get the error Fatal error: Children relation not eager loaded, use $ prefix to access: Children<Party, User>(for: [party_id]): file FluentKit/Children.swift, line 33 from the line
element.users.append(User(id: UUID(input.id), username: input.username, is_ready: input.is_ready))
When I comment this line the code runs and I get a return code.
I tried adding the prefix to element.$users and $User but then it complains about not being able to find element.$users and $User in scope.
Party model
import Fluent
import Vapor
final class Party: Model, Content {
static let schema = "parties"
struct JoinParty: Content {
let id: String
let username: String
let is_ready: Bool
}
struct Output: Content {
let code: String
}
#ID(key: .id)
var id: UUID?
#Field(key: "party_code")
var party_code: String
#Field(key: "host_id")
var host_id: UUID
#Field(key: "is_active")
var is_active: Bool
// change to Game when model is made
#Field(key: "selected_games")
var selected_games: [String]?
// change to Setting when model is made
#Field(key: "settings")
var settings: String
#Field(key: "results")
var results: Array<GameResult>?
#Children(for: \.$party)
var users: [User]
init() { }
init(id: UUID? = nil,
party_code: String,
host_id: UUID,
is_active: Bool,
selected_games: [String]? = nil,
settings: String,
results: Array<GameResult>? = nil) {
self.id = id
self.party_code = party_code
self.host_id = host_id
self.is_active = is_active
self.selected_games = selected_games
self.settings = settings
self.results = results
}
}
User model
import Fluent
import Vapor
final class User: Model, Content {
static let schema = "users"
struct Input: Content {
let id: UUID
let username: String
}
struct Output: Content {
let id: String
let username: String
}
#ID(key: .id)
var id: UUID?
#Field(key: "username")
var username: String
#Field(key: "is_ready")
var is_ready: Bool
#OptionalParent(key: "party_id")
var party: Party?
#Children(for: \.$user)
var gameResults: [GameResult]
init() {}
init(id: UUID? = nil, username: String, is_ready: Bool, partyID: UUID? = nil) {
self.id = id
self.username = username
self.is_ready = is_ready
self.$party.id = partyID
}
}
I have a similar function to update a username from a user already working which is almost the same thing.
func update(req: Request) throws -> EventLoopFuture<User.Output> {
let input = try req.content.decode(User.Input.self)
return User.find(input.id, on: req.db)
.unwrap(or: Abort(.notFound))
.flatMap { user in
user.username = input.username
return user.save(on: req.db)
.map { User.Output(id: user.id!.uuidString, username: user.username) }
}
}
Any help would be really appreciated.
Thank you in advance.
Unfortunately, adding children to a parent model is not that intuitive yet. I hope that someday we can get that added, but it's not there yet. Fluent 5 maybe?
Anyway, what you'll need to do instead is create your new User model, passing in the party's ID value to the partyID initializer parameter, and then save the User model.
let user = User(id: UUID(input.id), username: input.username, is_ready: input.is_ready, partyID: element.id)
return user.save(on: request.db)
So your method should end up looking like this:
func addToParty(req: Request) throws -> EventLoopFuture<Party.Output> {
guard let id = req.parameters.get("partyID", as: UUID.self) else {
throw Abort(.badRequest)
}
let input = try req.content.decode(Party.JoinParty.self)
return Party.find(id, on: req.db).unwrap(or: Abort(.notFound)).flatMap { element in
return User(
id: UUID(input.id),
username: input.username,
is_ready: input.is_ready,
partyID: element.id
).save(on: req.db)
}.transform(to: Party.Output(code: "200"))
}

Add user using Authentication + Create User

Currently, I have successfully implemented allowing a user to create an account using an email address and password and inserting that info into Firebase Authentication. What I'm trying to figure out now is how to simultaneously take the information entered in the sign up form and insert a new record into FireStore. I'd like to insert the user's email address, first and last name as a record.
I've tried adding the code to insert the record inside of my "SignUp" function, but that seems to break the function. Below is the working code followed by the code I tried that didn't work.
//Working Code
struct SignUpView: View {
#State var email: String = ""
#State var password: String = ""
#State var error: String = ""
#EnvironmentObject var session: SessionStore
let db = Firestore.firestore()
func signUp() {
session.signUp(email: email, password: password) { (result, error) in
if let error = error {
self.error = error.localizedDescription
} else {
self.email = ""
self.password = ""
}
}
}
//Does Not Work (Error: Type 'SignUpView' does not conform to protocol 'View')
struct SignUpView: View {
#State var email: String = ""
#State var password: String = ""
#State var error: String = ""
#State var fname: String = ""
#State var lname: String = ""
#EnvironmentObject var session: SessionStore
let db = Firestore.firestore()
func signUp() {
session.signUp(email: email, password: password) { (result, error) in
if let error = error {
self.error = error.localizedDescription
} else {
self.email = ""
self.password = ""
}
}
// Insert record into Users collection
db.collection("users").addDocument(data: [
"fname": fname,
"lname":lname,
"email": email ])
}
// End Insert
}
Session.SignUp is asynchronous.
struct SignUpView: View {
#State var email: String = ""
#State var password: String = ""
#State var error: String = ""
#State var fname: String = ""
#State var lname: String = ""
#EnvironmentObject var session: SessionStore
let db = Firestore.firestore()
func signUp() {
session.signUp(email: email, password: password) { (result, error) in
if let error = error {
self.error = error.localizedDescription
} else {
self.email = ""
self.password = ""
// Insert record into Users collection
db.collection("users").addDocument(data: [
"fname": self.fname,
"lname":self.lname,
"email": self.email ])
}
// End Insert
}
}
}
But the code you posted can not be the full code for your SignUpView because it needs to contain
var body: some View {}
Adding a user model to Firestore in SwiftUI is very simple. I used a completionBlock to handle completion, you can show a loading indicator till the function returned a value. This may be useful for all queries.
The following code will add a user to Firestore:
func addUserToDatabase(name: String, email: String, phone: String, completionBlock: #escaping (_ success: Bool) -> Void) {
let accountData = [
"name" : name,
"email" : email,
"phone" : phone
]
db.collection("users").document(self.user.id).setData(accountData) { err in
if err != nil {
completionBlock(false)
} else {
completionBlock(true)
}
}
}
Note that: 'self.user.id' is the user ID from the authentication. So when you create a user in the firebaseAuth, a unique ID will be created, you can store this ID in your app so you can create a reference in your database. Useful for easily removing or changing user's data.
Example of using this method in your app
self.addUserToDatabase(name: name, email: email, phone: phone) { (succes) in
if (succes) {
print("User added to the database")
UserDefaults.standard.set(self.user.id, forKey: "uid_current_user")
completionBlock(true)
} else {
print("Something went wrong when trying to add user to the database")
completionBlock(false)
}
}

Swift 2.0 - Get user details from firebase

I'm new to Swift and Firebase, and would like to know how to get a users details from Firebase by the userId.
I have the Firebase 'uid' stored in NSUserDefauls.
I have a user Model class like the following;
import UIKit
import Firebase
class User: NSObject {
var id: String?
var username: String?
var email: String?
var yogaLevel : Enums.Difficulty?
init(id: String, username: String, email: String){
self.id = id
self.username = username
self.email = email
}
override init(){
super.init()
}
}
I have a dataservie class that makes the relevant calls to Firebase;
import UIKit
import Firebase
class DataService{
static let dataService = DataService()
private var _BASE_REF = Firebase(url: "\(BASE_URL)")
private var _USER_REF = Firebase(url: "\(BASE_URL)/users")
var BASE_REF: Firebase {
return _BASE_REF
}
var USER_REF: Firebase {
return _USER_REF
}
var CURRENT_USER_REF: Firebase {
let userID = NSUserDefaults.standardUserDefaults().valueForKey("uid") as! String
let currentUser = Firebase(url: "\(BASE_REF)").childByAppendingPath("users").childByAppendingPath(userID)
return currentUser!
}
func createNewAccount(uid: String, user: Dictionary<String, String>) {
// A new user is created:
USER_REF.childByAppendingPath(uid).setValue(user)
}
}
I also have a manager class, that I would like to contains methods that return the users details.
import UIKit
import Firebase
class UserManager: NSObject {
static var currentUser = User()
class func getCurrentUserDetails() -> User{
// Attach a closure to read the data at our posts reference
DataService.dataService.CURRENT_USER_REF.observeEventType(.Value, withBlock: { snapshot in
currentUser.username = String(snapshot.value.objectForKey("username"))
currentUser.email = String(snapshot.value.objectForKey("email"))
currentUser.id = String(NSUserDefaults.standardUserDefaults().valueForKey("uid"))
print(String(currentUser.username) + " " + String(currentUser.email))
}, withCancelBlock: { error in
print("UserManager class - getCurrentUserDetails error: " + String(error.description))
})
return currentUser
}
}
I'd like advise on how I will create a function in the manager class the returns a 'user' that is populated from Firebase using the 'uid' I have stored. Can someone please point me in the right direction.
Please comment if you require more information, or more code.
This is a pretty open ended question so my answer is in the form of direction.
The current logged in user uid can be returned directly from Firebase:
if self.myRootRef.authData != nil {
print(ref.authData.uid) // user is authenticated
} else {
// No user is signed in
}
To answer the question(s) assume a Firebase structure of
root
users
uid_0
name: "Leroy"
email: "l.jenkins#ubrs.com"
Assume a User class
class UserClass: NSObject {
var name: String?
var email: String?
var uid: String?
}
Then, to populate a User class, the following code could be used to call a function
self.populateUser("uid_0")
here's the function to populate a User class from Firebase and add it to a Users array. That array could then be used to populate a tableView for example.
func populateUser(uid: String) {
let thisUserRef = self.usersRef.childByAppendingPath(uid)
thisUserRef.observeSingleEventOfType(.Value, withBlock: { snapshot in
if snapshot.value is NSNull {
print("snapshot was null");
} else {
let thisUser = UserClass()
thisUser.name = snapshot.value.objectForKey("name") as? String
thisUser.email = snapshot.value.objectForKey("email") as? String
thisUser.uid = uid
self.Users.append(thisUser)
print("\(thisUser.name!)")
}
})
}
self.Users is a class variable array defined thusly
var Users = [UserClass]()
Note 1: If the variables in your DataService class are global in nature, you can just create a file in your swift project and add them to a structure
MyGlobalVars.swift
struct MyGlobals {
static var rootRef = "app.firebase.com"
static var usersRef = "app.firebase.com/users"
}
Those can be accessed anywhere with
print("Global variable:\(MyGlobals.usersRef)")
Note 2: As in your Users class, you could just pass the snapshot to the Users class with an init and let it populate itself as well.
thisUser.populateFromSnapshot(snapshot.value)