Firebase Functions in swift does not return anything if there is no Internet - swift

There is a function that is triggered after the AccountKit authorization, it calls the Firebase Function to validate the token on Facebook and returns a userId if everything is confirmed, and registers the user if he has not yet been registered.
It works fine when Internet is available, but while offline - Firebase function does not return or throw any errors or at least nil results, and I would like it to return an error such as No internet connection or ANYTHING that could be catched.
Digging web and APIReference brought no results. Does the call of firebase function really not return anything in such cases (offline)?
func checkUserCredentials(phoneNumber: String, FBId: String, Token: String) {
functions.httpsCallable("checkUserCredentials").call(["phone":"\(phoneNumber)", "FBId":"\(FBId)", "Token":"\(Token)"])
{ (result, error) in
if let error = error as NSError?
{
if error.domain == FunctionsErrorDomain
{
let code = FunctionsErrorCode(rawValue: error.code)
let message = error.localizedDescription
}
}
if let userDoc = (result?.data as? [String: Any])?["userID"] as? String
{
DispatchQueue.main.async(execute: { self.performSegue(withIdentifier: "StartTheApp", sender: self) })
}
} }

I recommend checking for a network connection before making any network request. That way you're not dependent on the vagaries of whichever library you're using to talk to the network.
I use Reachability to check for a network connection before performing any requests (which I then perform using Alamofire). Below is a sample function to check for network:
import Reachability
...
func networkIsReachable(shouldShowAlert: Bool) -> Bool {
if let reachability: Reachability = Reachability(), reachability.connection != .none {
return true
}
if shouldShowAlert {
let alertController = UIAlertController(title: "Error", message: "No internet connection.", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil))
present(alertController, animated: true, completion: nil)
}
return false
}
Since I'm using this function all throughout my codebase, I even moved it into an extension so as not to violate DRY principle.
Updating your code to use this function would look like this:
func checkUserCredentials(phoneNumber: String, FBId: String, Token: String) {
guard let networkIsReachable(shouldShowAlert: true) else {
// network is not reachable, and user has been shown an error message
return
}
// now perform network request
// ...
}

Related

How to check if email already exist before creating an account (Swift)

I know different variations of this question have been asked. However I seem to keep running into the same issue every time.
I want to check if an email already exist before the user pushes onto the next view. I will enter an email that exist in the database and the performSegue func is always called and pushes the user as if that email does not exist.
The only way I can check officially is when the user reaches the final sign up VC and the Auth.auth().createUser(withEmail: email as! String, password: password as! String ) { (user, error) in code will check for all errors.
However for good user experience I would hate for the user to have to click back three times to change the email address. Here is the code I have for the enter email view controller.
// Check if email is already taken
Auth.auth().fetchSignInMethods(forEmail: emailTextField.text!, completion: { (forEmail, error) in
// stop activity indicator
self.nextButton.setTitle("Continue", for: .normal)
self.activityIndicator.stopAnimating()
if let error = error {
print("Email Error: \(error.localizedDescription)")
print(error._code)
self.handleError(error)
return
} else {
print("Email is good")
self.performSegue(withIdentifier: "goToCreateUsernameVC", sender: self)
}
})
First off am I even entering the create property in the forEmail section? I added emailTextField.text because its the only way I know how even get the email the user typed. Does anyone know a better way I can do this?
How I create user accounts
This is an example of what I use. When a user provides credentials, FirebaseAuth checks if these credentials can be used to make a user account. The function returns two values, a boolean indicating whether the creation was successful, and an optional error, which is returned when the creation is unsuccessful. If the boolean returns true, we simply push to the next view controller. Otherwise, we present the error.
func createUserAcct(completion: #escaping (Bool, Error?) -> Void) {
//Try to create an account with the given credentials
Auth.auth().createUser(withEmail: emailTextField.text!, password: passwordConfirmTextField.text!) { (user, error) in
if error == nil {
//If the account is created without an error, then we will make a ProfileChangeRequest, i.e. update the user's photo and display name.
if let firebaseUser = Auth.auth().currentUser {
let changeRequest = firebaseUser.createProfileChangeRequest()
//If you have a URL for FirebaseStorage where the user has uploaded a profile picture, you'll pass the url here
changeRequest.photoURL = URL(string: "nil")
changeRequest.displayName = self.nameTextField.text!
changeRequest.commitChanges { error in
if let error = error {
// An error happened.
completion(false, error)
} else {
//If the change is committed successfully, then I create an object from the credentials. I store this object both on the FirebaseDatabase (so it is accessible by other users) and in my user defaults (so that the user doesn't have to remotely grab their own info
//Create the object
let userData = ["email" : self.emailTextField.text!,"name": self.nameTextField.text!] as [String : Any]
//Store the object in FirebaseDatabase
Database.database().reference().child("Users").child(firebaseUser.uid).updateChildvalues(userData)
//Store the object as data in my user defaults
let data = NSKeyedArchiver.archivedData(withRootObject: userData)
UserDefaults.standard.set(data, forKey: "UserData")
UserDefaults.standard.set([Data](), forKey: "UserPhotos")
completion(true, nil)
}
}
}
} else {
// An error happened.
completion(false, error)
}
}
}
Here is an example of how I would use it. We can use the success boolean returned to determine if we should push to the next view controller, or present an error alert to the user.
createUserAcct { success, error in
//Handle the success
if success {
//Instantiate nextViewController
let storyboard = UIStoryboard(name: "Main", bundle: .main)
let nextVC = storyboard.instantiateViewController(withIdentifier: "NextVC") as! NextViewController
//Push typeSelectVC
self.navigationController!.pushViewController(viewController: nextVC, animated: true, completion: {
//We are no longer doing asynchronous work, so we hide our activity indicator
self.activityIndicator.isHidden = true
self.activityIndicator.stopAnimating()
})
} else {
//We now handle the error
//We are no longer doing asynchronous work, so we hide our activity indicator
self.activityIndicator.isHidden = true
self.activityIndicator.stopAnimating()
//Create a UIAlertController with the error received as the message (ex. "A user with this email already exists.")
let alertController = UIAlertController(title: "Error", message: error!.localizedDescription, style: .alert)
let ok = UIAlertAction(title: "OK", style: .cancel, action: nil)
//Present the UIAlertController
alertController.addAction(ok)
self.present(alertController, animated: true, completion: nil)
}
}
Let me know if this all makes sense, I know there is a lot to it. I'm just considering things you'll maybe find you need done anyways that you may not be aware of (like making change requests, or storing a data object on FirebaseDatabase).
Now for checking if the email is already taken:
Remember when I said that I post a user object to FirebaseDatabase upon account creation? Well we can query for the given email to see if it already exists. If it doesn't we continue with the flow as normal, without having actually created the account. Otherwise, we simply tell the user to pick another email address.
Pushing a user object to your database (taken from the above code):
if let firebaseUser = Auth.auth().currentUser {
//Create the object
let userData = ["email" : self.emailTextField.text!,"name": self.nameTextField.text!] as [String : Any]
//Store the object in FirebaseDatabase
Database.database().reference().child("Users").child(firebaseUser.uid).updateChildvalues(userData)
}
And now querying to see if somebody already has that email:
func checkIfEmailExists(email: String, completion: #escaping (Bool) -> Void ) {
Database.database().reference().child("Users").queryOrdered(byChild: "email").queryEqual(toValue: email).observeSingleEvent(of: .value, with: {(snapshot: DataSnapshot) in
if let result = snapshot.value as? [String:[String:Any]] {
completion(true)
} else {
completion(false)
}
}
}
Then we can call this like so:
checkIfEmailExists(email: emailTextField.text!, completion: {(exists) in
if exists {
//Present error that the email is already used
} else {
//Segue to next view controller
}
})

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!

AWS Mobile Hub states user is not signed-in after custom UI User Pool sign-in/authentication

I'm currently using AWS Mobile Hub for an iOS application that utilizes Cognito and Cloud Logic.
I decided to replace the default AuthUIViewController because I didn't like how it looked. I used this sample project to help me implement sign up through User Pools: https://github.com/awslabs/aws-sdk-ios-samples/tree/master/CognitoYourUserPools-Sample/Swift .
Here is my implementation:
Starting in my AppDelegate, I set the UserPool I want to sign into to a commonly accessible constant variable. One idea I have to why AWSMobileClient doesn't think my user is signed in is because it defines its own service configuration/pool, but I'm not sure:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
AWSDDLog.add(AWSDDTTYLogger.sharedInstance)
AWSDDLog.sharedInstance.logLevel = .verbose
// setup service configuration
let serviceConfiguration = AWSServiceConfiguration(region: Constants.AWS.CognitoIdentityUserPoolRegion, credentialsProvider: nil)
// create pool configuration
let poolConfiguration = AWSCognitoIdentityUserPoolConfiguration(clientId: Constants.AWS.CognitoIdentityUserPoolAppClientId,
clientSecret: Constants.AWS.CognitoIdentityUserPoolAppClientSecret,
poolId: Constants.AWS.CognitoIdentityUserPoolId)
// initialize user pool client
AWSCognitoIdentityUserPool.register(with: serviceConfiguration, userPoolConfiguration: poolConfiguration, forKey: AWSCognitoUserPoolsSignInProviderKey)
// fetch the user pool client we initialized in above step
Constants.AWS.pool = AWSCognitoIdentityUserPool(forKey: AWSCognitoUserPoolsSignInProviderKey)
return AWSMobileClient.sharedInstance().interceptApplication(
application, didFinishLaunchingWithOptions:
launchOptions)
}
After the AppDelegate is finished, the application goes to its root view controller named InitialViewController. Here, I allow the user to click a facebook sign in or regular (user pool) sign in.
class InitialViewController:UIViewController {
#objc func regLogin() {
//Set a shared constants variable "user" to the current user
if (Constants.AWS.user == nil) {
Constants.AWS.user = Constants.AWS.pool?.currentUser()
}
Constants.AWS.pool?.delegate = self
//This function calls the delegate function startPasswordAuthentication() in the extension below to initiate login
Constants.AWS.user?.getDetails().continueOnSuccessWith { (task) -> AnyObject? in
DispatchQueue.main.async(execute: {
//called after details for user are successfully retrieved after login
print(AWSSignInManager.sharedInstance().isLoggedIn)// false
print(AWSSignInManager.init().isLoggedIn)// false
print(AWSCognitoUserPoolsSignInProvider.init().isLoggedIn())// false
print(Constants.AWS.user?.isSignedIn) // true
AppDelegate.del().signIn()
})
return nil
}
}
}
extension InitialViewController: AWSCognitoIdentityInteractiveAuthenticationDelegate {
func startPasswordAuthentication() -> AWSCognitoIdentityPasswordAuthentication {
self.present(loginVC, animated: true, completion: nil)
return self.loginVC
}
}
As you can see, the functions do their job and the user is successfully logged in according to (Constants.AWS.user?.isSignedIn) as well as the fact that I am successfully able to retrieve the user details. However, when I ask AWSSignInManager or the UserPoolsSignInProvider whether my user is logged in, it returns false. This is a problem because without AWSMobileHub seeing my user as logged in, I cannot access my Cloud Logic functions etc.
Can someone please help me shed light on how I can notify MobileHub and the sign in manager that my user is logged into the user pool so that my application can work right?
Thank You!
Jonathan's answer is a good starting point for this, but it requires to include AWSAuthUI to your project.
A better solution is to implement directly the functions in AWSUserPoolsUIOperations.m. In particular, the function triggered when pressing the sign in button, should look like this:
#IBAction func signInPressed(_ sender: AnyObject) {
if (self.usernameTextField.text != nil && self.passwordTextField.text != nil) {
self.userName = self.usernameTextField.text!
self.password = self.passwordTextField.text!
AWSCognitoUserPoolsSignInProvider.sharedInstance().setInteractiveAuthDelegate(self)
AWSSignInManager.sharedInstance().login(
signInProviderKey: AWSCognitoUserPoolsSignInProvider.sharedInstance().identityProviderName,
completionHandler: { (provider: Any?, error: Error?) in
print(AWSSignInManager.sharedInstance().isLoggedIn)
})
} else {
let alertController = UIAlertController(title: "Missing information",
message: "Please enter a valid user name and password",
preferredStyle: .alert)
let retryAction = UIAlertAction(title: "Retry", style: .default, handler: nil)
alertController.addAction(retryAction)
}
}
Then include the following functions as an extension of your SignIn view controller:
public func handleUserPoolSignInFlowStart() {
let authDetails = AWSCognitoIdentityPasswordAuthenticationDetails(username: self.userName!, password: self.password!)
self.passwordAuthenticationCompletion?.set(result: authDetails)
}
public func startPasswordAuthentication() -> AWSCognitoIdentityPasswordAuthentication {
return self
}
public func getDetails(_ authenticationInput: AWSCognitoIdentityPasswordAuthenticationInput, passwordAuthenticationCompletionSource: AWSTaskCompletionSource<AWSCognitoIdentityPasswordAuthenticationDetails>) {
self.passwordAuthenticationCompletion = passwordAuthenticationCompletionSource
}
public func didCompleteStepWithError(_ error: Error?) {
DispatchQueue.main.async {
if let error = error as NSError? {
let alertController = UIAlertController(title: error.userInfo["__type"] as? String,
message: error.userInfo["message"] as? String,
preferredStyle: .alert)
let retryAction = UIAlertAction(title: "Retry", style: .default, handler: nil)
alertController.addAction(retryAction)
self.present(alertController, animated: true, completion: nil)
} else {
self.usernameTextField.text = nil
self.dismiss(animated: true, completion: nil)
}
}
}
After digging more through the AWS code, I found my answer in the pod AWSAuthUI in AWSSignInViewController.m (The view controller used in the default authentication process for mobile hub/cognito).
The code is:
- (void)handleUserPoolSignIn {
Class awsUserPoolsUIOperations = NSClassFromString(USERPOOLS_UI_OPERATIONS);
AWSUserPoolsUIOperations *userPoolsOperations = [[awsUserPoolsUIOperations alloc] initWithAuthUIConfiguration:self.config];
[userPoolsOperations loginWithUserName:[self.tableDelegate getValueForCell:self.userNameRow forTableView:self.tableView]
password:[self.tableDelegate getValueForCell:self.passwordRow forTableView:self.tableView]
navigationController:self.navigationController
completionHandler:self.completionHandler];
}
and getting to just the parts that matter... in Swift!!
userPoolsOperations.login(withUserName: "foo", password: "bar", navigationController: self.navigationController!, completionHandler: { (provider: Any?, error: Error?) in
print(AWSSignInManager.sharedInstance().isLoggedIn) // true
print(AWSSignInManager.init().isLoggedIn) // false
print(AWSCognitoUserPoolsSignInProvider.init().isLoggedIn()) // false
print(Constants.AWS.user?.isSignedIn) // nil
})
}
lesson learned: Reading through AWS's code is helpful even though it sucks

Should perform segue on request

i'm starting with Swift3 and i'm having a recurrent problem due to the asynchronism. But until now, i always find a solution with callback.
I have a textField and a button, when i click on the button, i check on the API if there is a existing user named as in the textField.
Using shouldPerformSegue, i return the value if the users exist or no.
I have a separated class for handling calls on the Api
class Api {
static let urlApi = "https://XXXXXXXXXXXXX"
private let CUSTOMER_ID = "XXXXXXXX"
private let CUSTOMER_SECRET = "XXXXXXXX"
private var access_token : String? = nil
private var userInfo : User?
init() {
self.connect()
}
func connect() {
// Do the connect...
}
func get(user: String, callback: #escaping (_ status: Bool) -> Void) {
Alamofire.request(URL(string: "\(Api.urlApi)/v2/users/\(user)")!,
method: .get,
parameters: nil,
encoding: URLEncoding.default,
headers: ["Authorization": "Bearer \(self.access_token!)"])
.responseJSON(completionHandler: { response in
if response.result.isFailure {
print("ERROR: GET USER", response)
callback(false)
} else {
print("SUCCESS Getting user ", user)
callback(true)
}
})
}
}
And in my shouldPerformSegue
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
var userExist : Bool? = nil
let dispatchQueue = DispatchQueue(label: "getUser")
let semaphore = DispatchSemaphore(value: 1)
dispatchQueue.sync {
self.api?.get(user: self.userTextField.text!, callback: { status in
userExist = status
print("1 USEREXIST", userExist)
})
}
semaphore.wait()
print("2 USEREXIST", userExist)
return userExist ?? false // always false because userExist == nil
}
Sorry for the function mess, i don't really find the right way to do my DispachQueue and my Semaphore .. All googling answer look that i need those
The proper way to handle this scenario would be to make the request when the user taps on the button. If there is an error, you would present some error that says the username already exists. Then they would try again.
If the request is successful and that username has not been taken, then you would call performSegueWithIdentifier. The link below shows a good demonstration of the steps to take after this. Your current implementation isn't necessary.
https://stackoverflow.com/a/37823730/653839

xmpp_messenger_ios Swift MUC swift

I am trying to do a MUC on iOS using xmpp_messenger_ios & XMPPFramework
Here is the code to join the room.
func createOrJoinRoomOnXMPP(){
// location has named array of lat and long
NSLog("Creating room on XMPP")
let roomJID: XMPPJID = XMPPJID.jidWithString(self.roomID + "#conference.ip-172-31-41-100")
let roomData: XMPPRoomCoreDataStorage = XMPPRoomCoreDataStorage.sharedInstance()
let chatRoom = XMPPRoom.init(roomStorage: roomData, jid: roomJID, dispatchQueue: dispatch_get_main_queue())
chatRoom.activate(OneChat.sharedInstance.xmppStream)
chatRoom.addDelegate(self, delegateQueue: dispatch_get_main_queue())
// let history = DDXMLElement.elementWithName("history")
// // Get lst messegs of the room
// history.addAttributeWithName("maxstanzas", stringValue: "10")
chatRoom.joinRoomUsingNickname(OneChat.sharedInstance.xmppStream!.myJID.user, history: nil)
}
as soon as this block executes I get an error in this code:
extension OneMessage: XMPPStreamDelegate {
public func xmppStream(sender: XMPPStream, didSendMessage message: XMPPMessage) {
if let completion = OneMessage.sharedInstance.didSendMessageCompletionBlock {
completion(stream: sender, message: message)
}
//OneMessage.sharedInstance.didSendMessageCompletionBlock!(stream: sender, message: message)
}
public func xmppStream(sender: XMPPStream, didReceiveMessage message: XMPPMessage) {
let user = OneChat.sharedInstance.xmppRosterStorage.userForJID(message.from(), xmppStream: OneChat.sharedInstance.xmppStream, managedObjectContext: OneRoster.sharedInstance.managedObjectContext_roster())
if !OneChats.knownUserForJid(jidStr: user.jidStr) { // <<< ERROR LINE
OneChats.addUserToChatList(jidStr: user.jidStr)
}
if message.isChatMessageWithBody() {
OneMessage.sharedInstance.delegate?.oneStream(sender, didReceiveMessage: message, from: user)
} else {
//was composing
if let _ = message.elementForName("composing") {
OneMessage.sharedInstance.delegate?.oneStream(sender, userIsComposing: user)
}
}
}
}
fatal error: unexpectedly found nil while unwrapping an Optional value
I have noticed that as soon as the connection is made to chat room it fetches previous messages, and thus the above code is executed.
Please help me out is doing a MUC for room chat on ios. I have searched and have not found any solution.
thanks
I solved this by this temporary solution.
extension OneMessage: XMPPStreamDelegate {
public func xmppStream(sender: XMPPStream, didSendMessage message: XMPPMessage) {
if let completion = OneMessage.sharedInstance.didSendMessageCompletionBlock {
completion(stream: sender, message: message)
}
//OneMessage.sharedInstance.didSendMessageCompletionBlock!(stream: sender, message: message)
}
public func xmppStream(sender: XMPPStream, didReceiveMessage message: XMPPMessage) {
NSLog("This is blocked")
// let user = OneChat.sharedInstance.xmppRosterStorage.userForJID(message.from(), xmppStream: OneChat.sharedInstance.xmppStream, managedObjectContext: OneRoster.sharedInstance.managedObjectContext_roster())
//
// if !OneChats.knownUserForJid(jidStr: user.jidStr) {
// OneChats.addUserToChatList(jidStr: user.jidStr)
// }
//
// if message.isChatMessageWithBody() {
// OneMessage.sharedInstance.delegate?.oneStream(sender, didReceiveMessage: message, from: user)
// } else {
// //was composing
// if let _ = message.elementForName("composing") {
// OneMessage.sharedInstance.delegate?.oneStream(sender, userIsComposing: user)
// }
// }
}
}
Blocking the OneMessage.swift code.
and handling the incoming messages in my ViewController.
This is not the right way to do it. but until ProcessOne give support for MUC this can be done.
Unwrapping that causes nil happens on:
user (return value of userForJID method is XMPPUserCoreDataStorageObject! )
jidStr (the type is String!)
Investigate which one happens to be nil.
Possible causes of user to be nil
- Nil value of jid or managedObjectContext is used in userForJID(:xmppStream:managedObjectContext)`
To find out which one is nil, simply do this:
guard let user = OneChat.sharedInstance.xmppRosterStorage.userForJID(message.from(), xmppStream: OneChat.sharedInstance.xmppStream, managedObjectContext: OneRoster.sharedInstance.managedObjectContext_roster())
else { fatalError("user is nil") }
guard let userJIDStr = user.jidStr
else { fatalError("jidStr is nil") }
I think you need to understand XMPP MUC first, read this doc.
When you send a message to MUCRoom, the serve will broadcast the message to all the members, including yourself.
And here message.from() = room.jid BUT NOT user.jid.
That's why the user you tried to get from roster is nil.