I am trying to set up a simple User object in my app, when I try to initialize the object or access it's parameters I'm not able to do so, it usually throws the following error:
Argument passed to call that takes no parameters
This feels like a beginner's issue but I have no idea why it's happening.
Here's the code I use for the object:
import Foundation
import Firebase
class User {
var username: String
var image: String
var email: String
init(username: String, image: String, email: String) {
self.username = username
self.image = image
self.email = email
}
func toAny() -> [String: Any] {
return ["username": username, "image": image, "email": email]
}
}
and then I imagine I should be able to do:
let user = User(username: "usr", image: "img", email: "mail")
but that doesn't work, it throws the error mentioned above
So I tried instantiating the object in the scope like:
let user: User?
and then
user.username = "usr"
but that didn't work either...
I have been working with Swift apps for over 2 years and I still have issues with the simplest of things sometimes, but since I can't find any great references online I thought I'd try my luck here. Though I agree it's a silly question I would love an explanation of what I'm doing wrong and how to never encounter this issue again.
It's due to the ambiguity between User created by you and the User class in the FirebaseAuth Framework
Try renaming your User class to something more specific to your use-case. Accessing its variables should not cause any issue.
Related
I am learning how to write tests for my API requests but having trouble setting up my test's completion code and response model.
I tried using an instance of UserResponse (let userResponse = UserResponse() ) however it required a value for its initializer (from: Decoder) and I have no idea what goes in there. The error I get is:
"Argument type 'Decoder.Protocol' does not conform to expected type 'Decoder'"
Also I am having errors in creating the test's completion handler, I am using the new Swift Result type (Result<UserResponse, Error>). The error I get is:
"Type of expression is ambiguous without more context"
This is an #escaping function but I got an error saying to remove #escaping in the test.
Any ideas on what is wrong? I have marked the trouble code below with comments.
Thank you!
// APP CODE
class SignupViewModel: ObservableObject {
func createAccount(user: UserSignup, completion: #escaping( Result<UserResponse, Error>) -> Void) {
AuthService.createAccount(user: user, completion: completion)
}
}
struct UserSignup: Encodable {
var username: String
var email: String
var password: String
}
struct UserResponse: Decodable {
var user: User
var token: String
}
struct User: Decodable {
var username: String
var email: String
// etc
{ private enum UserKeys }
init(from decoder: Decoder) throws { container / decode code }
}
// TEST CODE
class SignupViewModelTests: XCTestCase {
var sut: SignupViewModel!
override func setUpWithError() throws {
sut = SignupViewModel()
}
override func tearDownWithError() throws {
sut = nil
}
func testCreateAccount_WhenGivenSuccessfulResponse_ReturnsSuccess() {
let userSignup = UserSignup(username: "johnsmith", email: "john#test.com", password: "abc123abc")
// WHAT GOES IN from:??
let userResponse = UserResponse(from: Decoder)
// ERROR: "Type of expression is ambiguous without more context"??
func testCreateAccount_WhenGivenSuccessfulResponse_ReturnsSuccess() {
//arrange
let userSignup = UserSignup(username: "johnsmith", email: "john#test.com", password: "abc123abc")
sut.createAccount(user: UserSignup, completion: ( Result <UserResponse, Error> ) -> Void ) {
XCTAssertEqual(UserResponse.user.username, "johnsmith")
}
}
}
}
Okay there are multiple issues with the way you are unit testing your code, I wont go in detailing all of the issues, but what you need in gist to make it work is
func testCreateAccount() {
//given
let userSignup = UserSignup(username: "johnsmith", email: "john#test.com", password: "abc123abc")
let signupExpectation = expectation(description: "Sign up should succeed")
//when
sut.createAccount(user: userSignup) { (result) in
//then
switch result {
case .success(let response):
XCTAssertTrue(response.user.email == "john#test.com", "User email should be john#test.com")
XCTAssertTrue(response.user.username == "johnsmith", "Username should be johnsmith")
XCTAssertTrue(response.token != "", "Token should not be empty")
case .failure(let error):
XCTFail("Authorization should not fail, failed with \(error.localizedDescription)")
}
}
wait(for: [signupExpectation], timeout: 10.0)
}
You are trying to test an asynchronous API call so you cant test it with synchronous XCAssert statements you need expectation. There are ways to make the asynchronous API calls synchronous and test it with straight forward XCAssert statements if you are using third party libraries like RxTest. I think its out of scope for you considering you are still newbie to unit test with Swift.
You can read all about expectation here : Apple doc
I have followed a simple code structuring of Given, when and then as indicated by comment in answer, its a pretty neat way to arrange your code if you are not using any kind of third party library to write descriptive unit test like Quick and Nimble
There is a beautiful article on unit test using plain old XCTest framework in raywnderlich tutorial here.
Finally, as a closing remark, we dont make an actual API call to test our code in unit test, we write fakes and stubs to test out our code.
Assume if you end up writing unit test for your entire project and your CI/CD system starts running entire test suite for all your PRs and builds, and your unit test ends up making actual API call, amount of time that will be wasted in making an actual API call will increase your test suite run time rapidly making the release process a nightmare. Also testing backend API is not the intention of your unit test, hence avoid actual API call using mocks, fakes and stubs read about it :)
Unit test provided above is just to show you how to use expectation and test asynchronous API and definitely wont cover all possible cases :)
To create a UserResponse in test code, call its synthesized initializer, not the decoder initializer. Something like:
let userResponse = UserResponse(user: User("Chris"), token: "TOKEN")
And to create a closure in test code, you need to give it code. Completion closures in tests have one job, to capture how they were called. Something like:
var capturedResponses: [Result<UserResponse, Error>] = []
sut.createAccount(user: UserSignup, completion: { capturedResponses.append($0) })
This captures the Result that createAccount(user:completion:) sends to the completion handler.
…That said, it looks like your view model is directly calling a static function that makes a service call. If the test runs, will it create an actual account somewhere? Or do you have some boundary in place that we can't see?
Instead of directly testing createAccount(user:completion:), what you probably want to test is:
That a certain action (signing up) will attempt to create an account for a given user—but not actually do so.
Upon success, the view model will do one thing.
Upon failure, the view model will do another thing.
If my assumptions are correct, I can show you how to do this.
I'm trying to think in terms of API Design because ultimately I want to ship code to myself or others.
Let's make some assumptions in order to get a succinct scenario. We will assume I have some Code that authenticates with my server and returns a user object. Defined simply like this:
public struct User: Codable {
public let id: UUID
public let email: String
public let name: String
}
I'm writing this code as an SDK I would ship to myself or a third party where all the guts of AUTH are handled. Using Apples new Combine framework I might expose a publisher for the consumer of my SDK like this.
public enum CurrentUserError: Error {
case loggedOut
case sessionExpired
}
public struct MyAuthFrameworkPublishers {
static let currentUser: CurrentValueSubject<User?, CurrentUserError> = CurrentValueSubject<User?, CurrentUserError>(nil)
}
Now, my private auth could accomplish it's task and retrieve a user then publish that to anything outside the SDK that it listening like so:
class AuthController {
func authenticate() {
///Get authenticated user.
let user = User.init(id: UUID(), email: "some#some.com", name: "some")
MyAuthFrameworkPublishers.currentUser.send(user)
}
}
let authController = AuthController.init()
authController.authenticate()
Is there a way to keep or stop the user of this SDK from sending it's own User object to the publisher? Like a private or access controller .send() function in combine?
Do you really want to use CurrentUserError as your Failure type? Sending a failure ends any subscriptions to the subject and fails new ones immediately. If the session expires, don't you want to let the user log in again? That would require publishing another User? output, which you cannot do if you have published a failure.
Instead, use Result<User?, CurrentUserError> as your Output type, and Never as your Failure type.
Now, on to your question. In general, the way to prevent the user from calling send is to vend an AnyPublisher instead of a Subject:
public class AuthController {
public let currentUser: AnyPublisher<Result<User?, AuthenticationError>, Never>
public func authenticate() {
let user: User = ...
subject.send(user)
}
public init() {
currentUser = subject.eraseToAnyPublisher()
}
private let subject = CurrentValueSubject<Result<User?, AuthenticationError>, Never>(nil)
}
I have a password managing app. Currently, I store all of the user's accounts (i.e. Netflix, Spotify, etc) in an array that is then stored in User Defaults. Now, I want to make things more secure by storing each account in iOS keychain. I realized that even if I store each account in keychain, I will still need to keep track of them all in a data structure (for populating tableviews, etc) and that data structure will need to be stored somewhere. I am struggling to understand the best way to go about implementing this.
My current design approach is to have:
• An array of custom Account objects stored in User Defaults
• each Account in the array is stored in keychain
I am using Locksmith for working with keychain (Note: I'm not married to this framework).
Locksmith requires me to implement several protocols in my Account class. This is then confusing me when it comes to trying to encode Account objects into the master array being stored in User Defaults. I am trying to make Account conform to Codable and am royally confused about what is going/needs to go on on this point.
class Account: ReadableSecureStorable,
CreateableSecureStorable,
DeleteableSecureStorable,
GenericPasswordSecureStorable,
Codable {
var username: String // can i make these let's
var password: String
// Required by GenericPasswordSecureStorable
let service: String
var account: String { return username }
// Required by CreateableSecureStorable
var data: [String: Any] {
return ["password": password]
}
init(service: String, username: String, password: String) {
self.username = username
self.password = password
self.service = service
}
private enum CodingKeys: String, CodingKey {
case username
case password = "secret"
case service
}
}
struct AccountDefaults {
static private let accountsKey = "accountsKey"
static var accounts: [Account] = {
guard let data = UserDefaults.standard.data(forKey: accountsKey) else { return [] }
return try! JSONDecoder().decode([Account].self, from: data)
}() {
didSet {
guard let data = try? JSONEncoder().encode(accounts) else { return }
UserDefaults.standard.set(data, forKey: accountsKey)
}
}
}
I was getting errors stating that Account does not conform to decodable before adding the codingKeys enum.
From here on, I can't figure out where to go.
When I add an account, I can store it with Locksmith. Will the default codingKeys password override the real password I'm trying to save into keychain?
I know I'm not supposed to include multiple questions here, but the thoughts going through my head are:
What do I need to encode/decode?
How does using Locksmith affect what I need to encode/decode?
Will the password need to be encoded?
Codable is just an alias for Decodable & Encodable
where Decodable and Encodable are protocols.
So,
What do I need to encode/decode?
If you mean JSON,
For converting into JSON use default implementations. For simple structs just allow this protocol conformance.
How does using Locksmith affect what I need to encode/decode? Regarding this – no effect.
Will the password need to be encoded?
your password is a string. Obviously, we be easily encoded into JSON.
If you mean the password security, the Keychain with 'only unlocked' level is good.
I'm new to swift and I'm trying to make a sign-up form using multiple views. The problem is that after calling Auth.auth().currentUser?.uid
I get a different String in each view, being unable to continue doing work on one specific UID. What am I doing wrong?
I'm creating the user using the firebase function createUser as so:
Auth.auth().createUser(withEmail: emailField.text!, password:
passField.text!, completion: { (user, error) in
if let error = error {
print(error)
}
if let user = user {
let userInfo: [String : Any] = ["uid": UserModel().getUID(),
"username": self.usernameField.text!, "avatar_icon" : ""]
self.ref.child("users").child(user.uid).setValue(userInfo)
}
})
I've tried creating a model class that holds that specific string, but getting the same bad results.
import Foundation
import Firebase
class UserModel {
var userID : String
init() {
self.userID = (Auth.auth().currentUser?.uid)!
}
func getUID() -> String {
return self.userID
}
}
My first sign-up view controller is Register1ViewController, where I call UserModel.init(), getting the uid with UserModel().getUID() in the future view controllers.
At firebase every authenticated user have only one "unique" uID as you know. So when you logged in as a user, you should allways get the same uID when you query it. It is hard to say this from here because you provided little part of this code. You should provide more to get an answer.
EDIT:
It looks like you are calling createuser everytime your activity starts. That means you are creating another user every time you run the code. And the
Auth.auth().currentUser?.uid
returns you different uID. You should register once and log-in other times. You can check tutorial below. But dont just look snippets, open github code.
https://www.appcoda.com/firebase-login-signup/
I'm coding an app where a logged in user has got a couple of extra functionalities than a non logged in user. Basically, I have more or less 5 tabs. When I launch the app, the user immediately gets the login page. He can decide to skip it. If he skips it, there'll only be 3 tabs for him. If he logs in successfully, there'll be 5.
I already have my login page made. I just don't know how I can store a session if the user is logged in correctly, and only display a certain number of tabs if the user isn't. I come from PHP, I've just started learning Objective-C, so I'm looking for the same thing as $_SESSION in PHP, more or less.
So: if user logs in, store session, and show all the tabs. If he doesn't, only show a limited number of tabs.
How should I approach this?
In terms of storing the session, I assume username and password is enough.
You could store the username as you wish in NSUserDefaults or CoreData if you are using it. Storing a password is best using the keychain. SSKeychain makes it easy to do this.
[SSKeychain setPassword:password forService:myAppName account:userName]
You could store the fact they are logged in in-memory, but on app relaunch check by:
NSString *password = [SSKeychain passwordForService:myAppName account:userName];
if (password != nil)
{
// Logged in
}
If the user logs out, easy as deleting the password from the keychain by
[SSKeychain deletePasswordForService:myAppName account:userName]
Session handling is done automatically when you use NSURLConnection, so you can store the users data in a Sesssion on the server.
What you might be looking for is called a Singleton design pattern (some people reject it, but it can be very handy). What you do is create one object that is available everywhere in your code. In this object you for example store a BOOL that indicates whether the user has logged in or not. For example:
(I didn't run this, just to get the idea)
Mananger myManager* = [Manager sharedManager];
if(myManager.loggedIn){
//Show 5 tabs
}else{
//Show 3 Tabs
}
This code can be used in every class so you can always access your user's data. Manager would be a seperate class in this case that provides singleton functionality. Check out how to make one here: http://www.johnwordsworth.com/2010/04/iphone-code-snippet-the-singleton-pattern/
I'm gonna give you a comprehensive answer.
Don't use NSUserDefaults as session it's a bad solution
NSUserDefaults data is not encrypted, it may cause security issue.
Let's create a structured user class instead
When the user logged in, you will need to make sure you have access to user data throughout the app so you can get the data on any screen when you need it.
To achieve this, we need to make a great structure to organize this properly. Remember that current user and another users are both "user" so we will use the same class.
Create a class and name it "EDUser" (you can choose other name if you want).
This class will contain a user information (either current user or other user).
More than that, this class will have capability to log the user in.
Here's a picture of what the class might look like:
class EDUser {
var firstName: String
var lastName: String?
var birthDate: NSDate?
init(firstName: String, lastName: String?, birthDate: NSDate?) {
self.firstName = firstName
self.lastName = lastName
self.birthDate = birthDate
}
}
// MARK: - Accessor
extension EDUser {
class var currentUser: EDUser? {
get {
return loadCurrentUserFromDisk()
}
set {
saveCurrentUserToDiskWithUser(newValue)
}
}
}
// MARK: - Log in and out
extension EDUser {
class func loginWithUsername(username: String,
andPassword password: String,
callback: (EDUser?, NSError) -> Void) {
// Access the web API
var parameters = [
"username": username,
"password": password
]
YourNetworkingLibrary.request(.POST,
"https://api.yourwebsite.com/login",
parameters: parameters).responseJSON {
response in
if response.statusCode == .Success {
let user = EDUser(firstName: response["firstName"],
lastName: response["lastName"],
birthDate: NSDate.dateFromString(response["birthDate"]))
currentUser = user
callback(currentUser, nil)
} else {
callback(nil, yourError)
}
}
}
class func logout() {
deleteCurrentUserFromDisk()
}
}
// MARK: - Data
extension EDUser {
class private func saveCurrentUserToDiskWithUser(user: EDUser) {
// In this process, you encode the user to file and store it
}
class private func loadCurrentUserFromDisk() -> EDUser? {
// In this process, you get the file and decode that to EDUser object
// This function will return nil if the file is not exist
}
class private func deleteCurrentUserFromDisk() {
// This will delete the current user file from disk
}
}
// MARK: - Helper
extension NSDate {
class func dateFromString(string: String) -> NSDate {
// convert string into NSDate
}
}
Use Case
Now with everything in place, we can use it like this
Non-blocking logging in process
EDUser.loginWithUsername(username: "edward#domain.com",
password: "1234") {
user, error in
if error == nil {
// Login succeeded
} else {
// Login failed
}
}
Logging out
EDUser.logout()
Check whether the user is logged in
if EDUser.currentUser != nil {
// The user is logged in
} else {
// No user logged in
// Show the login screen here
}
Get current user data on any screen
if let currentUser = EDUser.currentUser {
// do something with current user data
}
Store other user as object
let user = EDUser(firstName: "Edward",
lastName: "Anthony",
birthDate: NSDate())