How to cancel a long polling request with PromiseKit and Moya - ios

I'm using PromiseKit and Moya to send a long polling request in the method bellow:
func pullMessages() {
let service = ChatServices()
let request = service.pullMessages()
self.request = request
request.promise.then { [weak self] status -> Void in
// If chat successfully established with the agent
// navigate to chat screen or else try again. And if failure then show error message
guard let `self` = self else { return }
switch status {
case .waiting:
self.pullMessages()
case .messages(let messages):
for message: String in messages {
self.addMessage(text: message, sender: self.agentSender())
}
self.pullMessages()
case .chatEnded(let reason):
if reason == .agent {
self.endChat(with: "Agent ended chat session")
}
case .failure:
self.endChat(with: "Session lost")
}
}.catch { error in
// Show error
Log.warning(error.localizedDescription)
self.endChat(with: "Session lost")
}
}
And I'm using self.request to cancel the request once I'm leaving the view in viewWillDisapear.
But after I leave the view, and a new message arrives. the "cancelled" request returns the message. Any ideas why this is happening?

Related

iOS: Calling AWSMobileClient initialize() makes getUserAttributes() not invoking callback

Here is a code snippet I am trying to get it work but without success so far. initialize() works fine but then getUserAttributes() is not triggering the callback. Not just getUserAttributes(), even other AWS calls such as getTokens() not triggering either. Believe, some where down inside AWS code, it is getting blocked. If I comment out initialize() then getUserAttributes() callback gets invoked. Tried various options with DispatchQueue/DispatchGroup, no help.
AWSMobileClient pod version 2.12.7.
import Foundation
import AWSMobileClient
struct AWSUser {
static let serialQueue = DispatchQueue(label: "serialQueue")
static let group = DispatchGroup()
static func initialize() -> Void {
DispatchQueue.global(qos: .background).async {
AWSInitialize()
getAWSUserAttributes()
}
}
static func AWSInitialize() -> Void {
group.enter()
AWSMobileClient.default().initialize { (userState, error) in
// error handling ...
switch userState {
case .signedIn:
//getAWSUserAttributes()
break
default:
break
}
group.leave()
}
}
static func getAWSUserAttributes() {
group.wait()
group.enter()
AWSMobileClient.default().getUserAttributes { (attrs, error) in
// NEVER REACHED!!!
// BUT WORKS IF AWSMobileClient.default().initialize() is commented out
group.leave()
}
}
}
For Getting Callback or trigger any event of AWSMobile Client, Make sure you have implemented below code in AppDelegate or respective view controller. If this method implement then function is trigger...
//Initialised Use Pool
func intializeUserPool() -> Void {
AWSDDLog.sharedInstance.logLevel = .verbose // TODO: Disable or reduce log level in production
AWSDDLog.add(AWSDDTTYLogger.sharedInstance) // TTY = Log everything to Xcode console
//Important for event handler
initializeAWSMobileClient()
}
// Add user state listener and initialize the AWSMobileClient
func initializeAWSMobileClient() {
AWSMobileClient.default().initialize { (userState, error) in
print("Initialise userstate:\(String(describing: userState)) and Info:\(String(describing: error))")
if let userState = userState {
switch(userState){
case .signedIn: // is Signed IN
print("Logged In")
print("Cognito Identity Id (authenticated): \(String(describing: AWSMobileClient.default().identityId))")
case .signedOut: // is Signed OUT
print("Logged Out")
print("Cognito Identity Id (unauthenticated): \(String(describing: AWSMobileClient.default().identityId))")
case .signedOutUserPoolsTokenInvalid: // User Pools refresh token INVALID
print("User Pools refresh token is invalid or expired.")
default:
self.signOut()
}
} else if let error = error {
print(error.localizedDescription)
}
}
//Register State
self.addUserStateListener() // Register for user state changes
}
// AWSMobileClient - a realtime notifications for user state changes
func addUserStateListener() {
AWSMobileClient.default().addUserStateListener(self) { (userState, info) in
print("Add useruserstate:\(userState) and Info:\(info)")
switch (userState) {
case .signedIn:
print("Listener status change: signedIn")
DispatchQueue.main.async {
self.getSession()
}
case .signedOut:
print("Listener status change: signedOut")
case .signedOutFederatedTokensInvalid:
print("Listener status change: signedOutFederatedTokensInvalid")
default:
print("Listener: unsupported userstate")
}
}
}

touchID implementation error cancelled by user

How to resolve the TouchId error: Domain=com.apple.LocalAuthentication Code=-2 "Canceled by user."
I tried to add local context again:
let myContext = LAContext()
let myLocalizedReasonString = "Please use your last login for Inspyrus Supplier Central."
var authError: NSError?
if #available(iOS 8.0, macOS 10.12.1, *) {
if myContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &authError) {
myContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: myLocalizedReasonString) { success, evaluateError in
DispatchQueue.main.async {
if success {
self.btnLoginClicked(UIButton())
} else {
print(evaluateError?.localizedDescription ?? "Failed to authenticate")
// Fall back to a asking for username and password.
// ...
}
}
}
}
}
You can check if the evaluateError returned from the evaluatePolicy call is a LAError.userCancel.
Something like this:
if success {
//...
}
else if let authError = evaluateError as? LAError {
switch authError.code {
case .userCancel:
// Authentication was canceled by user (e.g. tapped Cancel button).
break
default:
// Other error
break
}
// Or
switch authError {
case LAError.userCancel:
// Authentication was canceled by user (e.g. tapped Cancel button).
break
default:
// Other error
break
}
}

How to remove callback option UI of CallKit after ending the call

In my app i'm using CallKit for incoming call. There is no outgoing call feature in the app. Everything is fine but when the receiver or dailer ends the call it shows the CallKit UI with call back option. I don't want to show callback option, how can I do it?
My code for ending the call
func end(call: SpeakerboxCall) {
let endCallAction = CXEndCallAction(call: call.uuid)
let transaction = CXTransaction()
transaction.addAction(endCallAction)
requestTransaction(transaction, action: "endCall")
}
private func requestTransaction(_ transaction: CXTransaction, action:
String = "") {
callController.request(transaction) { error in
if let error = error {
print("Error requesting transaction: \(error)")
} else {
print("Requested transaction \(action) successfully")
}
}
}
I have solved it. I was force quitting the CallKit where the transaction is not correctly completing.
AppDelegate.shared.providerDelegate?.provider.reportCall(with: call.uuid, endedAt: nil, reason: CXCallEndedReason.remoteEnded)
We need to set endedAt to nil and reason to remoteEnded
Close All Call (Swift4)
func performEndCallAction() {
for call in self.cxCallController.callObserver.calls {
let endCallAction = CXEndCallAction(call: call.uuid)
let transaction = CXTransaction(action: endCallAction)
cxCallController.request(transaction) { error in
if let error = error {
NSLog("EndCallAction transaction request failed: \(error.localizedDescription).")
return
}
NSLog("EndCallAction transaction request successful")
}
}
}

Polling with RxSwift and Parse-Server

I'm working on an Apple TV app that uses Parse-Server as a backend and RxSwift and I'm trying to set up an authentication system similar to those on the tv streaming apps.
Right now I have an AuthenticationCode object in the parse database that has a code, device id, and session token column. I'm trying to use RxSwift's interval to perform a fetch on the object every 5 seconds, and am checking if the session token column has been filled out.
Here is the code:
func poll(authorizationCode: AuthorizationCode) -> Observable<AuthorizationCode> {
return Observable<Int>.interval(5, scheduler: MainScheduler.instance).flatMap({ _ in
return Observable<AuthorizationCode>.create { observer -> Disposable in
authorizationCode.fetchInBackground(block: { (authorizationCode, error) in
if let authorizationCode = authorizationCode as? AuthorizationCode {
observer.onNext(authorizationCode)
if authorizationCode.sessionToken != nil {
observer.onCompleted()
}
} else if let error = error {
observer.onError(error)
}
})
return Disposables.create()
}
})
}
I'm emitting on onNext event every time I fetch the object, and I want to terminate the sequence when the session code exists.
The problem I'm having with this code is that even after the session token is filled out and the onCompleted is called, the timer still fires and the subscriber never gets the onCompleted event.
Any help with this is appreciated.
Also, if I'm way off on how I should be doing this, let me know.
I would use Parse-Server live queries but they currently don't support tvOS.
Thanks.
UPDATED:
Try this:
func poll(authorizationCode: AuthorizationCode) -> Observable<AuthorizationCode> {
// 1. Return the Observable
return Observable<AuthorizationCode>.create { observer -> Disposable in
// 2. We create the interval here
let interval = Observable<Int>.interval(.seconds(5), scheduler: MainScheduler.instance)
// 3. Interval subscription
let subscription =
interval.subscribe(onNext: { _ in
// 4. Fetch
authorizationCode.fetchInBackground(block: { (authorizationCode, error) in
// 5. onNext, onCompleted, onError
if let authorizationCode = authorizationCode as? AuthorizationCode {
observer.onNext(authorizationCode)
if authorizationCode.sessionToken != nil {
observer.onCompleted()
}
} else if let error = error {
observer.onError(error)
}
})
})
return Disposables.create{
subscription.dispose()
}
}
}

If user does not exist should display an error Message with UIAlert

I am currently working on a IOS App using Swift 3. I am working on the login system. Verification of logging in works fine. The logic is that, if login succeeds, it goes to the next Screen. However, if user does not exist, it should display an error message with UIAlert. But when I try to display the UIAlert, i get an error that says "Assertion failure in -[UIKeyboardTaskQueue waitUntilAllTasksAreFinished]"
//Getting data from database
func getData() -> Void {
let url: String = "http://localhost/fridge_app/login.php" //this will be changed to the path where service.php lives
//created NSURL
let requestURL = NSURL(string: url)
//creating NSMutableURLRequest
var request = URLRequest(url: requestURL! as URL)
//setting the method to post
request.httpMethod = "POST"
//Getting values from textfield
let usernameVal = username.text
let passwordVal = password.text
//creating the post parameter by concatenating the keys and values from text field
let postString = "username=\(usernameVal!)&password=\(passwordVal!)";
print(postString)
request.httpBody = postString.data(using: String.Encoding.utf8)
//creating a task to send the post request
let task = URLSession.shared.dataTask(with: request as URLRequest){
data, response, error in
//exiting if there is some error
if error != nil{
print("error is \(error)")
return;
}
// Print out response string
var responseString: NSString?;
responseString = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
if(responseString == "invalid"){
self.isValid = false;
print(self.isValid)
}
if self.checkLogin(data: responseString!) == true {
self.performSegue(withIdentifier: "profileViewController", sender: self)
}
else{
print("Hello")
// It prints hello fine, but when it tries to run the showAlert function it fails
self.showAlert()
}
//print("responseString = \(self.responseString)")
}
//executing the task
task.resume()
}
This is the alert function
/*
* Show UIAlert Message
*/
func showAlert() -> Void{
let alert = UIAlertController(title: "User Does Not Exist",
message: "",
preferredStyle: UIAlertControllerStyle.alert)
let loginFail = UIAlertAction(title: "Close", style: .default, handler: nil);
alert.addAction(loginFail);
present(alert, animated: true)
}
This is method is called when user clicks login.
Unless you take special steps, the completion handlers for the tasks you submit to NSURLSession get run on a background thread. That means that any UI calls you do must be sent to the main thread or they don't work correctly (And may crash your app.)
The code in your completion handler is doing more UI work than just invoking an alert. You're also invoking a segue. If your completion handler doesn't do time-consuming work you might want to wrap the whole thing in a GCD call to the main thread:
DispatchQueue.main.async() {
//Put the entire body of your completion handler in here
}
Otherwise, you'll need to individually wrap each UI call in a call to the main queue like above.
EDIT:
Looking at your specific completion handler, you have one if/then/else block that does UIKit calls:
if self.checkLogin(data: responseString!) {
self.performSegue(withIdentifier: "profileViewController",
sender: self)
} else {
print("Hello")
self.showAlert()
}
So just wrap that part in a call to the main queue:
DispatchQueue.main.async() {
if self.checkLogin(data: responseString!) {
self.performSegue(withIdentifier: "profileViewController",
sender: self)
} else {
print("Hello")
self.showAlert()
}
}

Resources