User Class Design - swift

I am very new to design patterns, so I am in need of help determining a good way of designing a specific part of an iOS app I am working on.
I have a User object, that represents the user that is logged in. It is very simple, and looks like this:
class User {
var firstName: String
var lastName: String
var photo: String //must be stored as a string for the filename.
var listOfJoinedOpportunities = [Opportunity]()
var listOfJoinedOpportunitiesKeys = [String]()
var listOfFriendsOnTheApp: NSArray
var bio: String
var email: String
var userID: String //A unique ID that is used to persist data about the user to the database (Firebase).
init(firstName: String, lastName: String, photo: String, email:String, userID: String, joinedEvents: [Opportunity],joinedStrings: [String]){
self.firstName = firstName
self.lastName = lastName
self.photo = photo
self.listOfJoinedOpportunities = joinedEvents
self.listOfFriendsOnTheApp = []
self.listOfJoinedOpportunitiesKeys = []
self.bio = ""
self.email = email
self.userID = userID
}
}
Currently I am only in need of an instance that represents the logged in user, but I could foresee needing this class if I add features to interact with other users of the app.
A total of 5 views include interactions that need to read and write to the user object that represents the logged in user.
Currently, I am passing around the object from controller to controller by creating new references. OR, I am creating a new object that represents the same logged-in user from data that was saved to the database (Firebase).
Please help!

Passing the object to the destination view is actually the preferred way to handle this (prepareForSegue). That being said, you can also simply store the User object instance in your AppDelegate and reference it from any view easily if that fits your model.
The drawback to storing references in the App Delegate is clutter and it may be unnecessary in more complex cases since they'll always be allocated (lookup lazy loading as well). Generally you want to pass objects along so the previous views can dealloc which in turn lowers your memory footprint. This way if you receive a memory warning you can handle it appropriately to recover properly.
// Set a var in your appDelegate
var user:User?
// Accessing it from any view
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let loggedInUser = appDelegate.user

Related

How to match 2 users if they both pressed 'Heart' button

My app is very similar to Tinder, and only got the "matching" part left for it to be finished. When the user touches the 'Heart' button, a card with their profile is displayed on the Notifications View to the other user, for them to accept it or not.
In case of yes, the will both 'match' as in Tinder. My problem is in how to make that happen.
Person is presented to the User, and the home view displays the people, that UserManager holds.
struct Person: Identifiable, Hashable {
var username: String
var age: Int
}
struct User {
let username: String // These are let because they're being saved on UserManager
let age: Int
}
class UserManager: ObservableObject {
#Published var userInfo: UserInfo?
#Published var people: [Person] = [] // All People
#Published var matches: [Person] = [] // Matched people
Now, here in UserManager, I fetchAllUsers from Firebase and basically init the people's data by the documentSnapshot.
ref.getDocuments { documentsSnapshot, error in
if let error = error { }
documentsSnapshot?.documents.forEach({ snapshot in
let data = snapshot.data()
self.people.append(.init(data: data))
})
}
And finally, in HomeView, the user taps the button and appends the other person in the matches array, which doesn't make sense cause the other person hasn't even accepted them yet.
struct HomeView: View {
var body: some View {
CircleButtonView(type: .heart) {
if let person = userMng.people.last {
userMng.swipe(person, _direction: .like)
userMng.matches.append(person)
// Should change this to another array?
}
}
}
}
Tried saving it to Firebase and then retrieving the data from the users once matched but I can't especify what person the user has liked, for me to make that network call.
How can I append the liked person to another array and then append it to 'matches' once confirmed that they both like each other?
You need to make a new table in firebase with the name of firendRequest, where table child id will be userID and below it, we will have ids of all users who has sent him the friends request. Please have a look the the schema in below image which will make your concept more clear.
The way, you are getting all the user will add up more computation when number of users start increasing and its not recommend to do such large computation on mobile devices. One more thing, Firesbase is NO-SQL database so you need to duplicate your data to avoid computations. Making duplicate date does not mean you are not implementing the thing right :)

Where's the best place to call methods that interact with my database?

I'm creating an app that interacts with a Firestore database. As of now I have a singleton class, DatabaseManager that has all the methods relating to the Firestore database (i.e. get/post methods).
I have a User model called User that has properties such as name, email, photoURL, and some app-specific properties. Any user can edit their profile to update information from a view controller called EditProfileViewController.
Now my question is: is it best to call the DatabaseManager.shared.updateInfo(forUser: user) (where user is a User instance) from EditProfileViewController, User, or some other place?
Sorry if this is an obvious question, but there's going to be a lot of points in the app where I'll need similar logic so I wanted to know what's the best design. Also I'm sure this question has more to with MVC than it does Firebase/Swift.
A couple of thoughts:
Rather than accessing the singleton directly with, DatabaseManager.shared.update(for:), I might instead have a property for the database manager, initialize/inject it with the DatabaseManager.shared, and have whatever needs to interact with the database use that reference, e.g., dataManager.update(for:). The goal would be to allow your unit tests to mock a database manager if and when necessary.
I would not be inclined to have a view controller interact directly with the DatabaseManager. Many of us consider the view controller, which interacts directly with UIKit/AppKit objects, as part of the broader “V” of MVC/MVP/MVVM/whatever. We’d often extricate business logic (including interaction with the database manager) out of the view controller.
I personally wouldn’t bury it under the User object, either. I’d put it in an extension of the database manager, and called from the view model, the presenter, or whatever you personally want to call that object with the business logic.
Is there a reason you're using a singleton to contain all the Firestore logic? User model should contain the method updateInfo.
Here's an example i've used with Firestore:
class Group {
// can read the var anywhere, but an only set value in this class
private(set) var groupName: String!
private(set) var guestsInGroup: Int!
private(set) var joinedGroup: Bool!
private(set) var timeStampGroupCreated: Date!
private(set) var documentId: String!
init(groupName: String, guestsInGroup: Int, joinedGroup: Bool, timeStampGroupCreated: Date, documentId: String) {
self.groupName = groupName
self.guestsInGroup = guestsInGroup
self.joinedGroup = joinedGroup
self.timeStampGroupCreated = timeStampGroupCreated
self.documentId = documentId
}
// method to parse Firestore data to array, that table view will display
class func parseData(snapshot: QuerySnapshot?) -> [Group]{
var groups = [Group]()
guard let snap = snapshot else { return groups }
for document in snap.documents {
let data = document.data()
let groupName = data[GROUP_NAME] as? String ?? "No Group Name"
let guestsInGroup = data[GUESTS_IN_GROUP] as? Int ?? 0
let joinedGroup = data[JOINED_GROUP] as? Bool ?? false
let timeStampGroupCreated = data[TIMESTAMP_GROUP_CREATED] as? Date ?? Date()
let documentId = document.documentID
// add objects with fetched data into thoughts array
let newGroup = Group(groupName: groupName, guestsInGroup: guestsInGroup, joinedGroup: joinedGroup, timeStampGroupCreated: timeStampGroupCreated, documentId: documentId)
groups.append(newGroup)
}
return groups
}
}

Global Realm Object: Singleton or Fetch It Every Time?

In my app I need to have global access to a currentUser which is an instance of a User class defined like this:
class User: Object{
#objc dynamic var recordName = UUID().uuidString
#objc dynamic var name = ""
#objc dynamic var email = ""
#objc dynamic var photo: Data? = nil
override static func primaryKey() -> String? {
return "recordName"
}
}
The currentUser is established when the app launches, and I refer to it frequently almost everywhere in my app.
I've noticed that from time-to-time, I get an error that appears to be caused by referencing this currentUser in different places:
Realm accessed from incorrect thread
I'm able to keep track of which thread Realm is on most of the time, but it's difficult to cover all cases. So this leads me to my question.
Is there a safe way to set the currentUser object once as a singleton? Or should I save their ID to disk and then fetch the object from Realm every time I need it (something like below)?
let realm = try! Realm()
if let currentUserId = defaults.string(forKey: "currentUserId"), let user = realm.object(ofType: User.self, forPrimaryKey: currentUserId){
currentUser = user
}
I am using Swift 4.2 on Xcode 10. Thanks!
As long as you can make sure that you always access your currentUser object from the same thread, it's fine to set it up as a globally accessible object once and use that auto-updating reference to it instead of re-fetching it every time from Realm.
You can achieve this by either creating a dedicated thread to Realm and always dispatching to that thread before accessing Realm/currentUser or simply doing it from a system thread, such as DispatchQueue.main.

Getting no initialisation error in swift class

Hi am new to swift i am string to store user credential in user default from the struct object in a LoginViewController. Below is my code,
class User: NSObject {
//MARK: Properties
var name: String
var email: String
var loginData: LoginData
//MARK: Initialization
init(name: String, email:String, loginData: LoginData) {
self.name = name
self.email = email
self.loginData = loginData
}
}
Here LoginData is a structure
struct LoginData {
var username: String
var password: String
}
while submitting register data am assigning values as
let logindata = LoginData (username: username, password: password)
let v = User(name: nameval, email: emailval, loginData:logindata)
In LoginViewContoller I have a switch button to save user credential to NSUserdefaults I created
var logindataValue: LoginData
And storing in user defaults as follows
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(logindataValue.username, forKey: "username")
defaults.setObject(logindataValue.password, forKey: "password")
defaults.synchronize()
But in LoginViewController class showing an error as “LoginViewController has no initializers”
How to prevent this error? Is this the right approach to do in this scenario?
Swift is complaining that you aren't initializing the var logindataValue: LoginData in your LoginViewContoller. You can solve this a couple of ways.
Make logindataValue an Optional:
In your LoginViewController change the definition of logindataValue to this:
var logindataValue: LoginData?
It will be given a default initial value of nil. You'll have to unwrap it when you use it, but that's okay.
Give logindataValue a different default value:
In your LoginViewController change the definition of logindataValue to this:
var logindataValue = LoginData(username: "", password: "")
I don't recommend this approach. It doesn't seem right to create an empty LoginData object with meaningless values. It would be better to use an Optional value. But if there is some other meaningful default value that you could give, then this might work.
Add an initializer for LoginViewController:
This is the most complicated, as it is not easy to create custom initializers for subclasses of UIViewController. You will have to add an initializer that includes a logindataValue parameter that you can use to initialize logindataValue. But you will also have to override init?(coder: NSCoder) which is a pain in the you-know-what and probably not what you need.
So my advice is to make logindataValue an Optional.

Dynamically create objects from function

Im sorry in advance if my question is too silly, Im new to OOP and programming generally, but i need to figure out a way to create objects dynamically. For example I have a class with init like this
class User {
var name: String = ""
var age: Int = 0
init (name: String, age: Int) {
self.name = name
self.age = age
}
}
I can create new object by using
var newUser = User(name: "Joe", age: 21)
but i do it manually by hands in code.
How can I automate the process so every time I need to create an object, I pass name, age and object creates automatically with assigning to new variable without mentioning the variable name (for example, there is pattern to for creation a variable, so it does user1, user2, user3 and so on) ? Do I Have to create a specific builder function that creates instances of user?
If you want to create a large list of users for JSON or something without having to assign a bunch of variable names by hand, I would use a Dictionary and dynamically create the key and value. So a function would look like this
var dynamicallyAssignedName: String?
var dynamicallyAssignedAge: Int?
var users: Dictionary<String, User>?
func newUser(name: String, age: Int) {
var createUser = User(name: dynamicallyAssignedName, age: dynamicallyAssignedAge)
users[dynamicallyAssignedName] = createUser
}
Then you can upload the dictionary fairly easily.
You might mean something like this:
let arr = [("Matt", 61), ("Alexey", 123)]
let arr2 = arr.map {User(name:$0.0, age:$0.1)}