Swift Facebook Login Stopped Working - facebook

I'm having a very strange problem that I can't find the answer to. I had the parse login working perfectly. I added pods and now my app crashes when I try to log in to facebook. I've isolated the problem to the graph request never opening so the user is always nil. My login that used to work that now stopped looks like this:
func loginButton(loginButton: FBSDKLoginButton!, didCompleteWithResult result: FBSDKLoginManagerLoginResult!, error: NSError!) {
if ((error) != nil)
{
// Process error
}
else if result.isCancelled {
// Handle cancellations
//move to create account screen
let secondViewController = self.storyboard!.instantiateViewControllerWithIdentifier("LoginView") as! LoginViewController
self.navigationController!.pushViewController(secondViewController, animated: true)
}
else {
println("RESULT: \(result)") **<- Log shows result as "RESULT: <FBSDKLoginManagerLoginResult: 0x7f9271d83720>"**
//Logged in! Let's go to the game
returnUserData()
//set the controller
let secondViewController = self.storyboard!.instantiateViewControllerWithIdentifier("FriendsTableViewController") as! UITableViewController
//go to the controller
self.navigationController!.pushViewController(secondViewController, animated: true)
}
}
func returnUserData()
{
println("98")
let graphRequest : FBSDKGraphRequest = FBSDKGraphRequest(graphPath: "me", parameters: nil)
println("100")
graphRequest.startWithCompletionHandler({ (connection, result, error) -> Void in
println("102") **<-- NEVER GETS TO THIS**
if ((error) != nil)
{....
My pods file looks like this:
target 'AppName' do
pod 'AFNetworking', '~> 2.0'
pod 'JSQMessagesViewController', '>7.0'
end
I removed the Pods and it's still not working? Any insight as to why this could be? Thanks

Related

How to wait until get the response from component under test that use Alamofire? - Xcode

I have a login view controller that user Almofire library to get the response. I do the unit test on that controller but the test always fail. I think because take time to response.
My test case:
override func setUp() {
super.setUp()
continueAfterFailure = false
let vc = UIStoryboard(name: "Main", bundle: nil)
controllerUnderTest = vc.instantiateViewController(withIdentifier: "LoginVC") as! LoginViewController
controllerUnderTest.loadView()
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
controllerUnderTest = nil
super.tearDown()
}
func testLoginWithValidUserInfo() {
controllerUnderTest.email?.text = "raghad"
controllerUnderTest.pass?.text = "1234"
controllerUnderTest.loginButton?.sendActions(for: .touchUpInside)
XCTAssertEqual(controllerUnderTest.lblValidationMessage?.text , "logged in successfully")
}
I try to use:
waitForExpectations(timeout: 60, handler: nil)
But I got this error:
caught "NSInternalInconsistencyException"
almofire function in login presenter :
func sendRequest(withParameters parameters: [String : String]) {
Alamofire.request(LOGINURL, method: .post, parameters: parameters).validate ().responseJSON { response in
debugPrint("new line : \(response)" )
switch response.result {
case .success(let value):
let userJSON = JSON(value)
self.readResponse(data: userJSON)
case .failure(let error):
print("Error \(String(describing: error))")
self.delegate.showMessage("* Connection issue ")
}
self.delegate.removeLoadingScreen()
//firebase log in
Auth.auth().signIn(withEmail: parameters["email"]!, password: parameters["pass"]!) { [weak self] user, error in
//guard let strongSelf = self else { return }
if(user != nil){
print("login with firebase")
}
else{
print("eroor in somthing")
}
if(error != nil){
print("idon now")
}
// ...
}
}
}
func readResponse(data: JSON) {
switch data["error"].stringValue {
case "true":
self.delegate.showMessage("* Invalid user name or password")
case "false":
if data["state"].stringValue=="0" {
self.delegate.showMessage("logged in successfully")
}else {
self.delegate.showMessage("* Inactive account")
}
default:
self.delegate.showMessage("* Connection issue")
}
}
How can I solve this problem? :(
Hi #Raghad ak, welcome to Stack Overflow 👋.
Your guess about the passage of time preventing the test to succeed is correct.
Networking code is asynchronous. After the test calls .sendActions(for: .touchUpInside) on your login button it moves to the next line, without giving the callback a chance to run.
Like #ajeferson's answer suggests, in the long run I'd recommend placing your Alamofire calls behind a service class or just a protocol, so that you can replace them with a double in the tests.
Unless you are writing integration tests in which you'd be testing the behaviour of your system in the real world, hitting the network can do you more harm than good. This post goes more into details about why that's the case.
Having said all that, here's a quick way to get your test to pass. Basically, you need to find a way to have the test wait for your asynchronous code to complete, and you can do it with a refined asynchronous expectation.
In your test you can do this:
expectation(
for: NSPredicate(
block: { input, _ -> Bool in
guard let label = input as? UILabel else { return false }
return label.text == "logged in successfully"
}
),
evaluatedWith: controllerUnderTest.lblValidationMessage,
handler: .none
)
controllerUnderTest.loginButton?.sendActions(for: .touchUpInside)
waitForExpectations(timeout: 10, handler: nil)
That expectation will run the NSPredicate on a loop, and fulfill only when the predicate returns true.
You have to somehow signal to your tests that are safe to proceed (i.e. expectation is fulfilled). The ideal approach would be decouple that Alamofire code and mock its behavior when testing. But just to answer your question, you might want to do the following.
In your view controller:
func sendRequest(withParameters parameters: [String : String], completionHandler: (() -> Void)?) {
...
Alamofire.request(LOGINURL, method: .post, parameters: parameters).validate ().responseJSON { response in
...
// Put this wherever appropriate inside the responseJSON closure
completionHandler?()
}
}
Then in your tests:
func testLoginWithValidUserInfo() {
controllerUnderTest.email?.text = "raghad"
controllerUnderTest.pass?.text = "1234"
controllerUnderTest.loginButton?.sendActions(for: .touchUpInside)
let expectation = self.expectation(description: "logged in successfully)
waitForExpectations(timeout: 60, handler: nil)
controllerUnderTest.sendRequest(withParameters: [:]) {
expectation.fulfill()
}
XCTAssertEqual(controllerUnderTest.lblValidationMessage?.text , "logged in successfully")
}
I know you have some intermediate functions between the button click and calling the sendRequest function, but this is just for you to get the idea. Hope it helps!

Signing Out of Firebase in Swift

I am attempting to sign out of the Firebase API, but I can't seem to figure out how to handle any errors that may occur.
The Firebase pod provides a method for signing out:
FIRAuth.auth()?.signOut()
It is marked with throws, so I have wrapped it in a do/try/catch block in a method to test the signing out process:
do {
try FIRAuth.auth()?.signOut()
} catch (let error) {
print((error as NSError).code)
}
I see that the signOut method is marked with throws in the Firebase pod, but I don't see how it can handle any errors asynchronously. I have tried entering Airplane Mode, which triggers a network error in my code everywhere else that a network request takes place, but with the signOut method, that error isn't caught because I have no completion handler to execute from. All of the other authentication methods from the Firebase pods have a completion handler, in which I am able to handle errors.
Here is the documentation for the signOut method from the Firebase pod:
/** #fn signOut:
#brief Signs out the current user.
#param error Optionally; if an error occurs, upon return contains an NSError object that
describes the problem; is nil otherwise.
#return #YES when the sign out request was successful. #NO otherwise.
#remarks Possible error codes:
- #c FIRAuthErrorCodeKeychainError Indicates an error occurred when accessing the keychain.
The #c NSLocalizedFailureReasonErrorKey field in the #c NSError.userInfo dictionary
will contain more information about the error encountered.
*/
open func signOut() throws
Do you have any suggestions for an appropriate way to handle the signing out of a user when I don't have a completion handler that allows me to check for an error?
You can catch the error like this
do
{
try Auth.auth().signOut()
}
catch let error as NSError
{
print(error.localizedDescription)
}
Edited from Milli's answer to add sending user back to initial page of the app.
// log out
func logout(){
do
{
try Auth.auth().signOut()
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let IntroVC = storyboard.instantiateViewController(withIdentifier: "IntroVC") as! introVC
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.window?.rootViewController = IntroVC
}
catch let error as NSError
{
print(error.localizedDescription)
}
}
An error is highly unlikely to occur but it's never good to assume anything. By the sound of the documentation, it wipes out your keychain which is the only way you'd be able to log back into your firebase application. From trying logging out of my own firebase app I was surprise that 0 errors occured. Here is the original code.
#IBAction func logOutTapped(_ sender: Any) {
let firebaseAuth = FIRAuth.auth()
do {
try firebaseAuth?.signOut()
} catch let signOutError as NSError {
print ("Error signing out: %#", signOutError)
}
if Utility.hasFacebook {
let login = FBSDKLoginManager()
login.logOut()
}
if Utility.hasTwitter {
Twitter.sharedInstance().sessionStore.logOutUserID((Twitter.sharedInstance().sessionStore.session()?.userID)!)
}
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController = storyboard.instantiateViewController(withIdentifier: "LoginVC")
self.present(initialViewController, animated: false)
}
Anyways if you really want a completion handler then here's something I tossed up quickly
func logOut(completion:#escaping(_ errorOccured: Bool) -> Void) {
let firebaseAuth = FIRAuth.auth()
do {
try firebaseAuth?.signOut()
} catch let signOutError as NSError {
completion(true)
}
completion(false)
}

How can I do a Facebook login using Swift 2.0 and iOS 9.1?

I have added the latest Facebook SDK to my XCode 7.1 project written for iOS 9.1. Unfortunately all I know is Swift, not Objective C. Facebook's developer site documentation only has the documentation in Objective C. Every search on Google reveals documentation that is just too old because things have changed so much just in the past 6 months. So I'm kind of lost.
I did all the easy stuff with the plist file.
I was able to use:
import FBSDKCoreKit
import FBSDKLoginKit
I also added:
return FBSDKApplicationDelegate.sharedInstance().application(application, openURL: url, sourceApplication: sourceApplication, annotation: annotation)
and
return FBSDKApplicationDelegate.sharedInstance().application(application, didFinishLaunchingWithOptions: launchOptions)
To my AppDelegate.swift file. This all works and builds successfully. I have the correct frameworks added as well, obviously. Beyond this point I'm at a standstill because I don't know the syntax for adding a login button, for capturing what I assume would be returned json strings with tokens and other profile information I can use to store in the user's account, etc.
You're on the right track.
Have you set up your pList yet?
You're gonna have to add to your VC, add a login button, deal with delegate, and then deal with FB info. Something like (but not exactly) this:
class yourVC: UIViewController, FBSDKLoginButtonDelegate, UITextFieldDelegate
{
var loginView : FBSDKLoginButton = FBSDKLoginButton()
viewDidLoad() {
loginView.frame = CGRectMake(20, 20, theWidth-40, 40)
self.view.addSubview(loginView)
loginView.readPermissions = ["public_profile", "email", "user_friends","user_birthday"]
loginView.delegate = self
}
func loginButton(loginButton: FBSDKLoginButton!, didCompleteWithResult result: FBSDKLoginManagerLoginResult!, error: NSError!) {
if ((error) != nil)
{
//handle error
} else {
returnUserData()
}
}
func returnUserData()
{
let graphRequest : FBSDKGraphRequest = FBSDKGraphRequest(graphPath: "me", parameters: ["fields":"id,interested_in,gender,birthday,email,age_range,name,picture.width(480).height(480)"])
graphRequest.startWithCompletionHandler({ (connection, result, error) -> Void in
if ((error) != nil)
{
// Process error
print("Error: \(error)")
}
else
{
print("fetched user: \(result)")
let id : NSString = result.valueForKey("id") as! String
print("User ID is: \(id)")
//etc...
}
})
}
You're gonna have to play around with the returnData() part to get it right, but this code should get you 90% of the way there.
I've included a wide range of permissions (user age, interested in, etc), but you'll have to configure them yourself. Honestly this part (the hard part) is really similar with it's object C counterpart, which is much better documented. Just download some get projects where it's working, and try to parse them together into something that suits your fancy.
It can be hard to get it all working, but give it a go, and keep at it!
If you want to perform login from Facebook on click of your custom button, this will help:
#IBAction func onClickFacebookButton(sender: UIButton){
let login = FBSDKLoginManager()
login.loginBehavior = FBSDKLoginBehavior.SystemAccount
login.logInWithReadPermissions(["public_profile", "email"], fromViewController: self, handler: {(result, error) in
if error != nil {
print("Error : \(error.description)")
}
else if result.isCancelled {
}
else {
FBSDKGraphRequest(graphPath: "me", parameters: ["fields": "first_name, last_name, picture.type(large), email, name, id, gender"]).startWithCompletionHandler({(connection, result, error) -> Void in
if error != nil{
print("Error : \(error.description)")
}else{
print("userInfo is \(result))")
}
})
}
})
}

Swift 2.0 working Facebook custom login button

I beg all of thee. I searched everywhere but i can't find working swift 2.0 code for Facebook custom login button with returning Access Token. My code simply don't work. (token is not returning Facebook token)
Considering that you have done all the setup as described by the Facebook developer page, you need to do the following in your login view controller:
// Import the following on top below "import UIKit" in your login view controller
import FBSDKCoreKit
import FBSDKLoginKit
// This is your action code for your custom login button for login using facebook in Swift
#IBAction func fbLoginBtn(sender: AnyObject) {
let permisions = ["public_profile", "email"]
PFFacebookUtils.logInInBackgroundWithReadPermissions(permisions) {
(user: PFUser?, error: NSError?) -> Void in
if let error = error {
print(error)
} else {
if let user = user {
print(user)
// "yourSegue" below will be the segue identifier for your new view.
self.performSegueWithIdentifier("yourSegue", sender: self)
}
}
}
}
In case you have not setup your app at Facebook developer, please following the simple steps as mentioned in Facebook Developer Page.
1 - Add a normal button in your view then add the FB login class "FBSDKLoginButton"
2 - Declare your button in your ViewController
#IBOutlet weak var FBLoginButton: FBSDKLoginButton!
3 - Add in your ViewDidload methode
if (FBSDKAccessToken.currentAccessToken() != nil)
{
// User is already logged in, do work such as go to next view controller.
}
else
{
self.FBLoginButton.delegate = self
FBLoginButton.readPermissions = ["public_profile", "email", "user_friends"]
}
4-Add Delegate Login and logout button funcutions
func loginButton(loginButton: FBSDKLoginButton!, didCompleteWithResult result: FBSDKLoginManagerLoginResult!, error: NSError!) {
loginButton.readPermissions = ["public_profile", "email", "user_friends"]
let fbAccessToken = FBSDKAccessToken.currentAccessToken().tokenString
FBSDKGraphRequest(graphPath: "me?fields=id,name,email,gender,first_name,last_name,middle_name,birthday&access_token=\(fbAccessToken)", parameters: nil).startWithCompletionHandler({ (connection, result, error) in
if ((error) != nil) {
// Process error
print("Error: \(error)")
} else {
print("fetched user: \(result)")
}
})
}
func loginButtonDidLogOut(loginButton: FBSDKLoginButton!) {
print("User Logged Out")
}

Parse NSInternalInconsistencyException raises with PFImageView

I'm experiencing an odd behaviour of Parse right now.
My App worked fine for weeks of development and never raised a 'NSInternalInconsistencyException' since project setup.
But I just implemented a PFImageView in my ProfileViewController (second VC in UITabBarController, therefore only showing when user is logged in) and my App keeps crashing with this error:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'You have to call setApplicationId:clientKey: on Parse to configure Parse.'
Of course I checked my Parse setup in func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool:
Parse.setApplicationId("myApplicationID", clientKey: "myClientKey")
Parse.enableLocalDatastore()
// This is where I used to activate Parse, but SO and Parse forums kept
// saying you should try to do this at the very beginning
ParseCrashReporting.enable()
PFAnalytics.trackAppOpenedWithLaunchOptionsInBackground(launchOptions, block: nil)
In my ProfileViewController this is where I load my image from Parse:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
userImageView.file = user["profilePicture"] as PFFile
userImageView.loadInBackground { (image, error) -> Void in
if error != nil {
JSSAlertView().danger(self, title: "Loading Image Failed", text: "Please check your connection")
} else {
self.userImageView.image = image
}
}
}
Setting the instance variable of image:
var image: UIImage? {
// If User picked a new image, automatically update imageView
didSet {
userImageView.image = image
// Convert image to PNG and save in in Parse-Cloud
let imageData = UIImagePNGRepresentation(image)
let imageFile = PFFile(name: "profilePicture.png", data: imageData)
// Attach this file to our user
user["profilePicture"] = imageFile
user.saveInBackgroundWithBlock { (success, error) -> Void in
if error != nil {
JSSAlertView().danger(self, title: "Failed To Save Image", text: "Please try again saving your image!")
}
}
userImageView.file = user["profilePicture"] as PFFile
}
}
I have no clue what kind of connection could be between these things, but I didn't change anything else in the whole project.
Regards,
Ok, finally after hours of trial and error I found the solution for this problem.
You should never ever execute parse calls in the header of view controllers, as these calls could be executed before the API registration.
For example:
class ViewController: UIViewController {
// this will crash the app as above!
var user: PFUser! = PFUser.currentUser()
override func viewDidLoad() {
super.viewDidLoad()
// initialize the user here. This will work just fine
user = PFUser.currentUser()
}
}
Hope this saves you from having the same errors!