Create custom "read" Intent like GetCurrentLocation - ios

I'm creating shortcuts for a communication app. In this app there is a presence state (available, dnd, ...). I implemented an intent to set said presence state which worked fine. I created a "read" intent which just returns the currently set state, which works, but i can't pass the result to the next action.
For simplification and testing i created another intent just return a string, trying to process it, but i'm still unable to do that. My intent has no input parameters.
What am i missing? My Goal would be an interaction like Get Current Location offers.
class Return5IntentHandler: NSObject, Return5IntentHandling {
func handle(intent: Return5Intent, completion: #escaping (Return5IntentResponse) -> Void) {
completion(.success(result: "5"))
}
}

Classic case of searching for hours, writing a question only to find the answer 30min later yourself...
One has to explicitly set the output, which is the passed on value for following actions.

Related

SiriKit: How to set output property when intent is handled in the main app?

Context
I'm working on adding support for Siri Shortcuts (Intents?) to a non-trivial app. My main goal is to allow users to automatise some tasks within the app using Shortcuts.app.
I have defined MyIntent in the Intents.intentdefinition in the Intents Extension target, together with MyIntentResponse, which has an output property file: INFile.
Given the non-trivial nature of the app, I'm forwarding the intent from Intent Extension to the main app:
func handle(intent: MyIntent, completion: #escaping (MyIntentResponse) -> Void) {
// Can't handle the intent in the app extension yet, let the main app handle it
let response = MyIntentResponse(code: .continueInApp, userActivity: nil)
// TODO: How to update the response (with an output property) if the intent continues in the app?
completion(response)
}
Problem
Once the intent is forwarded to the app, the AppDelegate.application(_:continue:restorationHandler:) method is called with a user activity which also has the property interaction: INInteraction? set.
From the provided INInteraction I can get both the intent: INIntent and response: INIntentResponse to handle the intent accordingly.
What's missing, however, is a way how to communicate back to the Shortcuts.app about the result of the intent and provide the desired output property (file: INFile).
Question
Is there a way how to provide an intent response with an output property set if the intent is handled in the main app?
Details
Interestingly, there's a AppDelegate method that should handle this use case:
optional func application(_ application: UIApplication,
handle intent: INIntent,
completionHandler: #escaping (INIntentResponse) -> Void)
And the documentation says:
An app with an Intents app extension can use this method to handle an intent directly, instead of handling it in the app extension. You might use this method to implement workflows that you cannot implement easily in your extension. For example, you might use it to start or manage a user's workout session. If your app is not running, SiriKit launches your app in the background so that the Siri interface remains active.
More info: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/2887573-application
In theory, it should be possible just to call the completionHandler with a newly instantiated MyIntentResponse which would have file property set correctly.
However, this method is never called. Instead, the above mentioned AppDelegate.application(_:continue:restorationHandler:) is called.
If you use .handleInApp instead of .continueInApp in your Intents extension handle() function, then your app delegate's handleIntent() function will be called instead of the continue:restorationHandler() function. This will allow you to pass back INIntentResponse back to the extension in the completionHandler.

SiriKit Intent Handler not being called

I have an app that incorporated Siri Intents and they work well. You can have Siri launch the app and it will perform the action as expected. All of my intents need to happen in the app so I do not have an extension for them.
However using the Shortcuts app, when I use my shortcut the app will just stop in my app and not continue so Siri is not receiving the handler response.
Right now my delegate will open from an action inside of the NSUserActivity call and I will use a custom class to determine which Shortcut it is and perform said action.
I do have that custom class conforming to the IntentHandler Protocol for each action and I call a .success response in the completion handler.
Heres where I think I may be mistaken. I manually call the Handler protocol inside of my custom class.
func handleSiri(_ intent: INItent) {
if intent is ActionIntent {
func handle(intent: ActionIntent, completion: #escaping (ActionIntent Response) -> Void) {
let response = DisconnectIntentResponse.init(code: .success, userActivity: nil)
print("Intent was a success")
completion(response)
}
}
}
However Siri never completes. Should I be calling this manually? Do I need to have an extension call these functions?
The only reason why this is not called is because you have not added the Voice Intent extension into your dependencies list under Build Phase section for your App Target.

Swift: What does "completionHandler: ((Bool) -> Void)" mean? [duplicate]

This question already has answers here:
swift : Closure declaration as like block declaration
(2 answers)
Closed 5 years ago.
Can someone explain what completionHandler: ((Bool) -> Void) means?
For instance it appears when requesting camera access:
AVCaptureDevice.requestAccess(for: AVMediaType.depthData, completionHandler: (<#T##(Bool) -> Void#>))
I usually do this to check if access was granted or not:
func requestCamera() {
AVCaptureDevice.requestAccess(for: AVMediaType.video) { (response) in
if response {
print("true")
} else {
print("denied")
}
}
}
Obviously I do stuff there, but that doesn't matter here. I just want to understand what ((Bool) -> Void) means and why I have to use the completion handler here. With other functions I can just set the handler to nil, but in this case it expects a response in some way.
So what does this mean?
Read Closures section of the Swift documentation.
It is a completion closure that gets executed when the AVCaptureDevice.requestAccess is finished with requesting access from the user. It has one Bool parameter that is true/false according to whether the user granted the access or not. It is not optional, so you have to provide some closure - that was a decision of the AVCaptureDevice.requestAccess author, and it makes sense - if you are requesting the access, you are requesting it because you want to use AVCaptureDevice. Therefore the author expects you to react to completing the requestAccess in some way.
Closure expression syntax has the following general form:
{ (parameters) -> return type in
statements
}
The parameters in closure expression syntax can be in-out parameters, but they can’t have a default value. Variadic parameters can be used if you name the variadic parameter. Tuples can also be used as parameter types and return types.
completionHandler: ((Bool) -> Void) this means that you'll get a Boolean value in your closure and it will return nothing(Void). Just like a method.
You can find this more about in Apple's documentation about closures
(Bool) -> Void means a closure take accepts a Bool as a argument and return a Void (i.e. nothing).
You provide a completion handler because requestAccess execute asynchronously. When the user has decided what permission to give your app, iOS will call the completion handler to continue execution of your program.
The majority of closures are used to handle some information that will be available later, for instance, a back end request that will take some seconds to respond, or even some action is required before some operation.
When you call a function that has a completion handler, it's the same as asking for something in the future, that will execute when the information is available, it's a great solution for asynchronous stuff or when you need to wait for some action, and then continue when you got the necessary data.

subscriberVideoDisabled Always returns OTSubscriberVideoEventPublisherPropertyChanged

I added a
(void)subscriberVideoDisabled:(OTSubscriberKit *)subscriber reason:(OTSubscriberVideoEventReason)reason
to check when the video is disabled by a subscriber/ publisher. I disable the video by putting wifi off or by going to the background while the broadcast is going on.
Above delegate method is triggered in both the scenarios. However, the reason enum does not return as
OTSubscriberVideoEventSubscriberPropertyChanged
when the video is disabled by the subscriber using any of the above methods. It returns as
OTSubscriberVideoEventPublisherPropertyChanged
which I think is not correct. Is there anything I am doing wrong?
This question is asked at OpenTok Developer forum as well
I believe the reason enum is qualityChanged because it's actually your client that pull off wifi which results in no internet condition. OTSubscriberVideoEventSubscriberPropertyChanged enum describes that situation when the video remote stream(publisher) is disabled explicitly.
There are a couple things you want to look into:
optional public func subscriberVideoDisableWarning(_ subscriber: OTSubscriberKit!)
optional public func subscriberVideoDisableWarningLifted(_ subscriber: OTSubscriberKit!)
optional public func subscriberDidReconnect(toStream subscriber: OTSubscriberKit!)

Handling errors in Swift

In my application I need to download a JSON file from the web. I have made a ResourceService class that have a download method as seen below. I use this service in "higher level" services of my app. You can see there are multiple of things that may go wrong during the download. The server could be on fire and not be able to successfully respond at the moment, there could be go something wrong during the moving of the temporary file etc.
Now, there is probably not much a user can do with this other than trying later. However, he/she probably want to know that there was something wrong and that the download or the behaviour of the "higher level" methods could not succeed.
Me as a developer is confused as this point because I don't understand how to deal with errors in Swift. I have a completionHandler that takes an error if there was one, but I don't know what kind of error I should pass back to the caller.
Thoughts:
1) If I pass the error objects I get from the NSFileManager API or the NSURLSession API, I would think that I am "leaking" some of the implementation of download method to the callers. And how would the caller know what kind of errors to expect based on the error? It could be both.
2) If I am supposed to catch and wrap those errors that could happen inside the download method, how would that look like?
3) How do I deal with multiple error sources inside a method, and how would the code that calls the method that may throw/return NSError objects look like?
Should you as a caller start intercepting the errors you get back and then write a lot of code that differentiates the messages/action taken based on the error code? I don't get this error handling stuff at all and how it would look like when there are many things that could go wrong in a single method.
func download(destinationUrl: NSURL, completionHandler: ((error: NSError?) -> Void)) {
let request = NSURLRequest(URL: resourceUrl!)
let task = downloadSession.downloadTaskWithRequest(request) {
(url: NSURL?, response: NSURLResponse?, error: NSError?) in
if error == nil {
do {
try self.fileManager.moveItemAtURL(url!, toURL: destinationUrl)
} catch let e {
print(e)
}
} else {
}
}.resume()
}
First of all this is a great question. Error handling is a specific task that applies to a incredible array of situations with who know's what repercussions with your App's state. The key issue is what is meaningful to your user, app and you the developer.
I like to see this conceptually as how the Responder chain is used to handle events. Like an event traversing the responder chain an error has the possibility of bubbling up your App's levels of abstraction. Depending on the error you might want to do a number of things related to the type of the error. Different components of your app may need to know about error, it maybe an error that depending on the state of the app requires no action.
You as the developer ultimately know where errors effect your app and how. So given that how do we choose to implement a technical solution.
I would suggest using Enumerations and Closures as to build my error handling solution.
Here's a contrived example of an ENUM. As you can see it is represents the core of the error handling solution.
public enum MyAppErrorCode {
case NotStartedCode(Int, String)
case ResponseOkCode
case ServiceInProgressCode(Int, String)
case ServiceCancelledCode(Int, String, NSError)
func handleCode(errorCode: MyAppErrorCode) {
switch(errorCode) {
case NotStartedCode(let code, let message):
print("code: \(code)")
print("message: \(message)")
case ResponseOkCode:
break
case ServiceInProgressCode(let code, let message):
print("code: \(code)")
print("message: \(message)")
case ServiceCancelledCode(let code, let message, let error):
print("code: \(code)")
print("message: \(message)")
print("error: \(error.localizedDescription)")
}
}
}
Next we want to define our completionHandler which will replace ((error: NSError?) -> Void) the closure you have in your download method.
((errorCode: MyAppErrorCode) -> Void)
New Download Function
func download(destinationUrl: NSURL, completionHandler: ((errorCode: MyAppErrorCode) -> Void)) {
let request = NSURLRequest(URL: resourceUrl!)
let task = downloadSession.downloadTaskWithRequest(request) {
(url: NSURL?, response: NSURLResponse?, error: NSError?) in
if error == nil {
do {
try self.fileManager.moveItemAtURL(url!, toURL: destinationUrl)
completionHandler(errorCode: MyAppErrorCode.ResponseOkCode)
} catch let e {
print(e)
completionHandler(errorCode: MyAppErrorCode.MoveItemFailedCode(170, "Text you would like to display to the user..", e))
}
} else {
completionHandler(errorCode: MyAppErrorCode.DownloadFailedCode(404, "Text you would like to display to the user.."))
}
}.resume()
}
In the closure you pass in you could call handleCode(errorCode: MyAppErrorCode) or any other function you have defined on the ENUM.
You have now the components to define your own error handling solution that is easy to tailor to your app and which you can use to map http codes and any other third party error/response codes to something meaningful in your app. You can also choose if it is useful to let the NSError bubble up.
EDIT
Back to our contrivances.
How do we deal with interacting with our view controllers? We can choose to have a centralized mechanism as we have now or we could handle it in the view controller and keep the scope local. For that we would move the logic from the ENUM to the view controller and target the very specific requirements of our view controller's task (downloading in this case), you could also move the ENUM to the view controller's scope. We achieve encapsulation, but will most lightly end up repeating our code elsewhere in the project. Either way your view controller is going to have to do something with the error/result code
An approach I prefer would be to give the view controller a chance to handle specific behavior in the completion handler, or/then pass it to our ENUM for more general behavior such as sending out a notification that the download had finished, updating app state or just throwing up a AlertViewController with a single action for 'OK'.
We do this by adding methods to our view controller that can be passed the MyAppErrorCode ENUM and any related variables (URL, Request...) and add any instance variables to keep track of our task, i.e. a different URL, or the number of attempts before we give up on trying to do the download.
Here is a possible method for handling the download at the view controller:
func didCompleteDownloadWithResult(resultCode: MyAppErrorCode, request: NSURLRequest, url: NSURL) {
switch(resultCode) {
case .ResponseOkCode:
// Made up method as an example
resultCode.postSuccessfulDownloadNotification(url, dictionary: ["request" : request])
case .FailedDownloadCode(let code, let message, let error):
if numberOfAttempts = maximumAttempts {
// Made up method as an example
finishedAttemptingDownload()
} else {
// Made up method as an example
AttemptDownload(numberOfAttempts)
}
default:
break
}
}
Long story short: yes
... and then write a lot of code that differentiates the
messages/action taken based on the error code?
Most code examples leave the programmer alone about how to do any error handling at all, but in order to do it right, your error handling code might be more than the code for successful responses. Especially when it comes to networking and json parsing.
In one of my last projects (a lot of stateful json server communication) I have implemented the following approach: I have asked myself: How should the app possibly react to the user in case of an error (and translate it to be more user friendly)?
ignore it
show a message/ an alert (possibly only one)
retry by itself (how often?)
force the user to start over
assume (i.e. a previously cached response)
To achieve this, I have create a central ErrorHandler class, which does have several enums for the different types of errors (i.e. enum NetworkResponseCode, ServerReturnCode, LocationStatusCode) and one enum for the different ErrorDomains:
enum MyErrorDomain : String {
// if request data has errors (i.e. json not valid)
case NetworkRequestDomain = "NetworkRequest"
// if network response has error (i.e. offline or http status code != 200)
case NetworkResponseDomain = "NetworkResponse"
// server return code in json: value of JSONxxx_JSON_PARAM_xxx_RETURN_CODE
case ServerReturnDomain = "ServerReturnCode"
// server return code in json: value of JSONxxxStatus_xxx_JSON_PARAM_xxx_STATUS_CODE
case ServerStatusDomain = "ServerStatus"
// if CLAuthorizationStatus
case LocationStatusDomain = "LocationStatus"
....
}
Furthermore there exists some helper functions named createError. These methods do some checking of the error condition (i.e. network errors are different if you are offline or if the server response !=200). They are shorter than you would expect.
And to put it all together there is a function which handles the error.
func handleError(error: NSError, msgType: String, shouldSuppressAlert: Bool = false){
...
}
This method started with on switch statement (and needs some refactoring now, so I won't show it as it still is one). In this statement all possible reactions are implemented. You might need a different return type to keep your state correctly in the app.
Lessons learned:
Although I thought that I have started big (different enums, central user alerting), the architecture could have been better (i.e. multiple classes, inheritance, ...).
I needed to keep track of previous errors (as some are follow ups) in order to only show one error message to the user -> state.
There are good reasons to hide errors.
Within the errorObj.userInfo map, it exits a user friendly error message and a technicalErrorMessage (which is send to a tracking provider).
We have introduced numeric error codes (the error domain is prefixed with a letter) which are consistent between client and server. They are also shown to the user. This has really helped to track bugs.
I have implemented a handleSoftwareBug function (which is almost the same as the handleError but much less cases). It is used in a lot of else-blocks which you normally do not bother to write (as you think that this state can never be reached). Surprisingly it can.
ErrorHandler.sharedInstance.handleSoftwareBug("SW bug? Unknown received error code string was code: \(code)")
How does it look like in code: There are a lot of similar backend network requests where a lot of code looks something like the following:
func postAllXXX(completionHandler:(JSON!, NSError!) -> Void) -> RegisteringSessionTask {
log.function()
return postRegistered(jsonDict: self.jsonFactory.allXXX(),
outgoingMsgType: JSONClientMessageToServerAllXXX,
expectedIncomingUserDataType: JSONServerResponseAllXXX,
completionHandler: {(json, error) in
if error != nil {
log.error("error: \(error.localizedDescription)")
ErrorHandler.sharedInstance.handleError(error,
msgType: JSONServerResponseAllXXX, shouldSuppressAlert: true)
dispatch_async(dispatch_get_main_queue(), {
completionHandler(json, error)
})
return
}
// handle request payload
var returnList:[XXX] = []
let xxxList = json[JSONServerResponse_PARAM_XXX][JSONServerResponse_PARAM_YYY].arrayValue
.....
dispatch_async(dispatch_get_main_queue(), {
completionHandler(json, error)
})
})
}
Within the above code you see that I call a completionHandler and give this caller the chance to customize error handling, too. Most of the time, this caller only handles success.
Whenever I have had the need for retries and other and not so common handling, I have also done it on the caller side, i.e.
private func postXXXMessageInternal(completionHandler:(JSON!, NSError!) -> Void) -> NSURLSessionDataTask {
log.function()
return self.networkquery.postServerJsonEphemeral(url, jsonDict: self.jsonFactory.xxxMessage(),
outgoingMsgType: JSONClientMessageToServerXXXMessage,
expectedIncomingUserDataType: JSONServerResponseXXXMessage,
completionHandler: {(json, error) in
if error != nil {
self.xxxMessageErrorWaitingCounter++
log.error("error(\(self.xxxMessageErrorWaitingCounter)): \(error.localizedDescription)")
if (something || somethingelse) &&
self.xxxMessageErrorWaitingCounter >= MAX_ERROR_XXX_MESSAGE_WAITING {
// reset app because of too many errors
xxx.currentState = AppState.yyy
ErrorHandler.sharedInstance.genericError(MAX_ERROR_XXX_MESSAGE_WAITING, shouldSuppressAlert: false)
dispatch_async(dispatch_get_main_queue(), {
completionHandler(json, nil)
})
self.xxxMessageErrorWaitingCounter = 0
return
}
// handle request payload
if let msg = json[JSONServerResponse_PARAM_XXX][JSONServerResponse_PARAM_ZZZ].stringValue {
.....
}
.....
dispatch_async(dispatch_get_main_queue(), {
completionHandler(json, error)
})
})
}
Here is another example where the user is forced to retry
// user did not see a price. should have been fetched earlier (something is wrong), cancel any ongoing requests
ErrorHandler.sharedInstance.handleSoftwareBug("potentially sw bug (or network to slow?): no payment there? user must retry")
if let st = self.sessionTask {
st.cancel()
self.sessionTask = nil
}
// tell user
ErrorHandler.sharedInstance.genericInfo(MESSAGE_XXX_PRICE_REQUIRED)
// send him back
xxx.currentState = AppState.zzz
return
For any request, you get either an error or an http status code. Error means: Your application never managed to talk properly to the server. http status code means: Your application talked to a server. Be aware that if you take your iPhone into the nearest Starbucks, "your application talked to a server" doesn't mean "your application talked to the server it wanted to talk to". It might mean "your application managed to talk to the Starbucks server which asks you to log in and you have no idea how to do that".
I divide the possible errors into categories: "It's a bug in my code". That's where you need to fix your code. "Something went wrong, and the user can do something about it". For example when WiFi is turned off. "Something went wrong, maybe it works later". You can tell the user to try later. "Something went wrong, and the user can't do anything about it". Tough. "I got a reply from the server that I expected. Maybe an error, maybe not, but something that I know how to handle". You handle it.
I also divide calls into categories: Those that should run invisibly in the background, and those that run as a result of a direct user action. Things running invisibly in the background shouldn't give error messages. (Bloody iTunes telling me it cannot connect to the iTunes Store when I had no interest in connecting to the iTunes Store in the first place is an awful example of getting that wrong).
When you show things to the user, remember that the user doesn't care. To the user: Either it worked, or it didn't work. If it didn't work, the user can fix the problem if it is a problem they can fix, they can try again later, or it's just tough luck. In an enterprise app, you might have a message "call your help desk at xxxxxx and tell them yyyyyy".
And when things don't work, don't annoy the user by showing error after error after error. If you send then requests, don't tell the user ten times that the server is on fire.
There are things that you just don't expect to go wrong. If you download a file, and you can't put it where it belongs, well, that's tough. It shouldn't happen. The user can't do anything about it. (Well, maybe they can. If the storage of the device is full then you can tell the user). Apart from that, it's the same category as "Something went wrong, and the user can't do anything about it". You may find out as a developer what the cause is and fix it, but if it happens with an application out in the user's hands, there's nothing reasonable you can do.
Since all such requests should be asynchronous, you will always pass either one or two callback blocks to the call, one for success and one for failure. I have most of the error handling in the download code, so things like asking the user to turn WiFi on happen only once, and calls may even be repeated automatically if such an error condition is fixed by the user. The error callback is mostly used to inform the application that it won't get the data that it wanted; sometimes the fact that there is an error is useful information in itself.
For consistent error handling, I create my own errors representing either errors returned by the session, or html status codes interpreted as errors. Plus two additional errors "user cancelled" and "no user interaction allowed" if either there was a UI involved and the user cancelled the operation, or I wanted to use some user interaction but wasn't allowed to. The last two errors are different - these errors will never be reported to the user.
I would wrap the errors in your own, but pass the underlying error as a property on your error class (ala C#'s InnerException). That way you are giving consumers a consistent interface, but also providing lower level error detail if required. However, the main reason I would do this is for unit testing. It makes it much easier to mock your ResourceService class and test the code paths for the various errors that could occur.
I don't like the thought of passing back an array of errors, as it adds complexity for the consumer. Instead I would provide an array of InnerException instances. If they are instances of your own error class, they would potentially have their own InnerException's with underlying errors. However, this would probably only make sense if you were doing your own validations where multiple errors might make sense. Your download method will probably have to bail out after the first error encountered.

Resources