Skip to content
This repository has been archived by the owner on Aug 22, 2019. It is now read-only.

Getting the current user object #22

Closed
sefasenturk95 opened this issue Mar 20, 2016 · 21 comments
Closed

Getting the current user object #22

sefasenturk95 opened this issue Mar 20, 2016 · 21 comments

Comments

@sefasenturk95
Copy link

Hi there love the library. However I'm having trouble getting the current user object after being logged in. I'm curious about the fact if I need to manually subscribe after logging in or will it do it automatically?
Also is there any example code of instructions I might've missed that implement the basics.

Also I'd like to add that I'm using Meteor accounts and I'm wondering how nested properties like the "profile" property will look like class wise.

Any help at all would be very welcome! Excellent job so far!

@ashmore11
Copy link

Have you published you user on the server:

Meteor.publish('user',  function() { 
    return Meteor.users.findOne(this.userId())
});

and subscribed on the client:

Meteor.subscribe("user") {
    // Do something when the user subscription is ready...
}

Regarding the profile property, I'm not 100% sure but I'm thinking something like this might work:

class User: MeteorDocument {
    var id: String?
    var profile: [String: AnyObject]?
}

then just find the email like so:

var email = profile.valueForKey("email")

@sefasenturk95
Copy link
Author

Hi ashmore11,

Thanks for your reply. I have published a user via the Meteor server. I'll post my current code:

Meteor Server:

Meteor.publish("currentUser", function () {
    return Meteor.users.find(this.userId);
});

Swift code:
Server.swift

import Foundation
import SwiftDDP

protocol ServerDelegate : class {
    func connected();
}

class Server {

    static let sharedInstance = Server()
    weak var delegate: ServerDelegate?
    var currentUser = MeteorCollection<User>(name: "users")

    private init() {
        print("Connecting..")
        self.connect()
    }

    private func connect() {
        Meteor.client.logLevel = .None

        Meteor.connect("ws://127.0.0.1:8081/websocket") {
            print("Connected to the DDP Server");
            self.delegate?.connected()
        }
    }

    func logIn(emailAddress: String, password: String, callback: (result: Bool) -> Void) {
        Meteor.loginWithPassword(emailAddress, password: password) { result, error in

            if error == nil {

                Meteor.subscribe("currentUser");      
                callback(result: true)
            } else {
                callback(result: false)
            }
        }
    }
}

User.swift

import Foundation
import SwiftDDP

class User: MeteorDocument {
    var _id: String?
    var services: [String: AnyObject]?
    var emails: NSMutableArray?
    var roles: NSMutableArray?
    var profile: [String: AnyObject]?

}

DashboardViewController.swift

import UIKit
import SwiftDDP

class DashboardViewController: UIViewController {

    let MH : Server = Server.sharedInstance

    override func viewDidLoad() {
        super.viewDidLoad()

        self.title = "Dashboard"

        print(MH.currentUser.valueForKey("profile"))
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

After logging in the Dashboard VC is loaded and the print result in a crash..

@ashmore11
Copy link

Are you calling your logIn function on the Server class anywhere?

Also, shouldn't this:

var currentUser = MeteorCollection<User>(name: "users")

not be this:

var currentUser = MeteorCollection<User>(name: "currentUser")

@sefasenturk95
Copy link
Author

I'm calling the logIn method on the LoginViewController.swift

class LoginViewController: UIViewController, ServerDelegate {

    @IBOutlet weak var emailAddress: UITextField!
    @IBOutlet weak var password: UITextField!
    @IBOutlet weak var logInBtn: UIButton!

    var MH : Server = Server.sharedInstance


    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.

        self.title = "Login";

        print("Login")

        self.MH.delegate = self;
//        
//        if (!self.MH.DDPClient.connected) {
//            logInBtn.userInteractionEnabled = false;
//        }
    }

    func connected() {
        logInBtn.userInteractionEnabled = true;
    }

    @IBAction func attemptLogIn(sender: AnyObject) {
        if emailAddress.text == "" {
            return print("Email is empty")
        }

        if password.text == "" {
            return print("Password is empty")
        }



        self.MH.logIn(emailAddress.text!, password: password.text!, callback: { result in

            if result {
                print("Logged in")

                self.presentViewController(UINavigationController(rootViewController: DashboardViewController(nibName: "DashboardViewController", bundle: nil)), animated: true, completion: nil)
            } else {
                print("Username and/ord password incorrect.")
            }

        })


    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

I changed it to currentUser now but no difference, it still crashes.

@ashmore11
Copy link

Are you able to get any information from the crash? If you change the Meteor log level to Info, are there any clues?

@siegesmund
Copy link
Owner

This looks generally correct to me. It would be helpful to know why the compiler says it's crashing? Could you post the crash message?

@sefasenturk95
Copy link
Author

2016-03-21 13:58:20.023 Moneyhub[18830:1268005] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<_TtGC8SwiftDDP16MeteorCollectionC8Moneyhub4User_ 0x7fdde8c207f0> valueForUndefinedKey:]: this class is not key value coding-compliant for the key profile.'
*** First throw call stack:
(
    0   CoreFoundation                      0x0000000108b12e65 __exceptionPreprocess + 165
    1   libobjc.A.dylib                     0x000000010a852deb objc_exception_throw + 48
    2   CoreFoundation                      0x0000000108b12aa9 -[NSException raise] + 9
    3   Foundation                          0x0000000108f6e888 -[NSObject(NSKeyValueCoding) valueForUndefinedKey:] + 226
    4   Foundation                          0x0000000108ec4997 -[NSObject(NSKeyValueCoding) valueForKey:] + 280
    5   Moneyhub                            0x000000010871c32a _TFC8Moneyhub23DashboardViewController11viewDidLoadfS0_FT_T_ + 410
    6   Moneyhub                            0x000000010871c432 _TToFC8Moneyhub23DashboardViewController11viewDidLoadfS0_FT_T_ + 34
    7   UIKit                               0x00000001094c5f98 -[UIViewController loadViewIfRequired] + 1198
    8   UIKit                               0x00000001094c62e7 -[UIViewController view] + 27
    9   UIKit                               0x00000001095188b0 -[UINavigationController preferredContentSize] + 194
    10  UIKit                               0x000000010949d0aa -[UIPresentationController preferredContentSizeDidChangeForChildContentContainer:] + 59
    11  UIKit                               0x0000000109499438 __56-[UIPresentationController runTransitionForCurrentState]_block_invoke + 95
    12  UIKit                               0x000000010933b4a2 _runAfterCACommitDeferredBlocks + 317
    13  UIKit                               0x000000010934ec01 _cleanUpAfterCAFlushAndRunDeferredBlocks + 95
    14  UIKit                               0x000000010935aaf3 _afterCACommitHandler + 90
    15  CoreFoundation                      0x0000000108a3e367 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
    16  CoreFoundation                      0x0000000108a3e2d7 __CFRunLoopDoObservers + 391
    17  CoreFoundation                      0x0000000108a33f2b __CFRunLoopRun + 1147
    18  CoreFoundation                      0x0000000108a33828 CFRunLoopRunSpecific + 488
    19  GraphicsServices                    0x000000010f97dad2 GSEventRunModal + 161
    20  UIKit                               0x000000010932f610 UIApplicationMain + 171
    21  Moneyhub                            0x000000010871facd main + 109
    22  libdyld.dylib                       0x000000010b7c692d start + 1
    23  ???                                 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb) 

@sefasenturk95
Copy link
Author

@ashmore11 Unfortunately the log info does not provide any clues. It just subscribes and crashes and provides me with the crash message as above.

@siegesmund
Copy link
Owner

MH.currentUser refers to an instance of MeteorCollection, which is not key-value coding compliant. That's what it's telling you.

Getting the current user the way you have it set up should look something like this:

var id = Meteor.client.userId()
var user = MH.currentUser.findOne(id)

That should return your user, as an instance of your User class.

MeteorCollection is pretty basic. Have a look at the methods it offers. It's probably just fine for user management, but for bigger problems, you might want to consider building on top of it - adding/overriding methods of your own or rolling your own.

@sefasenturk95
Copy link
Author

@siegesmund Thanks for your reply. I've changed my code in the DashboardViewController to:

class DashboardViewController: UIViewController {

    let MH : Server = Server.sharedInstance

    override func viewDidLoad() {
        super.viewDidLoad()

        self.title = "Dashboard"

        let currentUser = MH.currentUser.findOne(Meteor.client.userId()!)

        print(currentUser!.valueForKey("profile"))
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

When I run the app it crashes with this fatal error:

unexpectedly found nil while unwrapping an Optional value

@siegesmund
Copy link
Owner

You have two optionals in that code, either of which could be causing the error, but you haven't specified which one. I'm betting it's the second one.

If it's the first one - Meteor.client.userId() - that indicates that your login hasn't completed. But as I read your code, it seems your login should have completed.

So... A couple of things to note:

Pub/Sub isn't instant. After you login - which is async and takes a little bit of time, then you're subscribing to currentUser, which is async and takes a little bit of time to send the sub request, and to get the pub data - in this case your user.

Looking at your login function, you call the callback before the publication has finished, triggering the invocation of your dashboard VC, which expects the publication to be ready/finished. But it's async and probably won't be. That means your user collection will still be empty. That means your currentUser will be nil if you force unwrap it.

Putting your callback in the subscription completion closure will ensure that it runs only after the sub is complete. I don't know if this will solve all of your problems here, but It should pop one off of the stack.

func logIn(emailAddress: String, password: String, callback: (result: Bool) -> Void) {
        Meteor.loginWithPassword(emailAddress, password: password) { result, error in

            if error == nil {

                Meteor.subscribe("currentUser") {
                                callback(result: true)
                } 
            } else {
                callback(result: false)
            }
        }
    }

@sefasenturk95
Copy link
Author

Hi @siegesmund I logged the Meteor.client.userId() and it was filled so the problem is not there.

I've added the completion closure to my logIn method, but it still crashes on the same point when I try to log the profile..

@siegesmund
Copy link
Owner

If you're logged in, and you can get the client userId, then this is a problem with your implementation of MeteorCollection.

As @ashmore11 suggested - the log info is going to be your friend here. Set log level to debug. You'll be able to see every DDP message that is passed between your client and server. That will make is easy to determine exactly what the client is receiving - when it gets your user data, and what user data was passed.

@sefasenturk95
Copy link
Author

This is the debug log from app start to crash..

[Debug] [DDP] [0x7ff3a0d2a890] [249] ddpMessageHandler > Received message: {
    id = A327942FB8b14B088c8580615CDaCB96;
    msg = result;
    result =     {
        id = 56e88a1a2edc34620700475c;
        token = "HO4bV8ketJY5a3ZwmZMNDcwmUZN_ZnZhKZXq96Rh98b";
        tokenExpires =         {
            "$date" = 1466281968073;
        };
    };
}
[Debug] [DDP] [0x7ff3a0d2a890] [249] ddpMessageHandler > Received message: {
    collection = users;
    fields =     {
        roles =         (
        );
        services =         {
            password =             {
                bcrypt = "$2a$10$JeruMaZiIkn2Q6KlPknExuixr349MmBt/2cxX.EIk4kKfDmrbG15a";
            };
            resume =             {
                loginTokens =                 (
                                        {
                        hashedToken = "gkxCP5Z7uDLk1bASJomfn6+/vsZjYLkSC0cpAD5gSV8=";
                        when =                         {
                            "$date" = 1458505796150;
                        };
                    },
                                        {
                        hashedToken = "LTQPcGfRmBpnyhn76p0iPfWMCtj7wOXLxYkaEzMpIlE=";
                        when =                         {
                            "$date" = 1458505968073;
                        };
                    }
                );
            };
        };
        subscription =         {
            end =             {
                "$date" = 1490041760661;
            };
            type = 1;
        };
    };
    id = "-56e88a1a2edc34620700475c";
    msg = changed;
}
[Debug] [DDP] [0x7ff3a0e10230] [249] ddpMessageHandler > Received message: {
    msg = ready;
    subs =     (
        4799450059680112780
    );
}
Logged in
56e88a1a2edc34620700475c
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb) 

@siegesmund
Copy link
Owner

Notice how the message says 'collection=users'?

Change your user collection to MeteorCollection<User>(name: "users").

I see you changed it earlier in the thread to 'currentUser'. That's the name of your publication, not your collection.

@sefasenturk95
Copy link
Author

@siegesmund Yes, I've noticed that but unfortunately it does not solve the problem.

@SwapnilBGaikwad
Copy link

I think you can implement the User class something similar to given below:

class User: MeteorDocument {
var id: String?
var profile: [String: AnyObject]?

override func setValue(value: AnyObject?, forKey key: String) {
switch(key) {
case "profile":
profile = value as? [String: AnyObject]
break
}
}
}

setValue is the method which will get called when documentWasAdded method get's called and set the attributes in the User class.

I think this is the method which will actually map the NSDictionary data to our User class and set the attributes.
hope this will help you.

@iain17
Copy link

iain17 commented Mar 23, 2016

Yep, got it working. @Sef1995 I can post a code example if you're still having trouble. The key is to override setValue. Might be good to add a little example for people in the docs? Anyways this issue can be closed.

Edit: Maybe overriding setValue in MeteorDocument is a good idea, catch the exception if can't set the value?

@sefasenturk95
Copy link
Author

@SwapnilBGaikwad Thanks for your reply, I got it working now by overriding it just like you suggested!

@siegesmund
Copy link
Owner

It looks like you've figured this out, so I'm closing this issue.

@jaddoescad
Copy link

Hello iain17 , could you post your example code?

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants