SiriKit and Payment Domain
Intro
Since it’s first release of SiriKit, Apple extended support for many domains. Let’s review payment domain. Specifically, accounts aka balance aka quick balance check. After reading this tutorial following will look familiar:
- INSearchForAccountsIntent
- INSearchForAccountsIntentResponse
- INPaymentAccount
Our goal is to ask Siri something like “How much money I have?” and get list of accounts in reply.
Preparation
Application will have 3 main parts:
- main application (main target; simple single screen app)
- SiriKit extension (target to interact with Siri)
- shared library (where accounts fetching logic is); code to share between previous targets.
First, create empty project (File > New > Project… >Single View App) named MyBank
. Second, add shared library (File > New > Target… > Cocoa Touch Framework) and use QuickBalance
for name. Our main target should depend on shared library.
SiriKit
To get Siri know about our app, let’s enable Siri
(in “Project navigator” tab select project > Capabilities > Siri). Next, select the “Info.plist” add new row “Privacy — Siri Usage Description
” with custom text.
At some point app must ask permission for Siri. In our app we put it in app delegate. Open AppDelegate.swift
, import Intents
framework and add in application(_:didFinishLaunchingWithOptions:)
permission request:
import Intents...func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { INPreferences.requestSiriAuthorization { status in if status == .authorized { print("Hello, Siri!") } else { print("No Siri :(") } } return true}
Run app. You should see blank screen and dialog
Accounts
Every bank application should have accounts. We also create some. To share code between main app and Siri extensions, we put domain object Account
and logic to fetch accounts from server inside QuickBalance
framework. Let’s create new class (select QuickBalance folder in project structure tree, File > New > File… > Swift File) named Account
. We keep Account
pretty simple:
public struct Account { public let name: String public let balance: Double}
And we also need some service to call (select QuickBalance folder in project structure tree, File > New > File… > Swift File), let’s name in QuickBalanceService
. Our service returns three dummy accounts with delay (pretending it is doing real network call):
open class QuickBalanceService { public init() {} public func quickBalanceAccounts(_ completion: @escaping ((_ accounts: [Account]) -> Void)) { var accounts = [Account]() let account1 = Account(name: "Main Account", balance: 150.0) let account2 = Account(name: "Saving Account", balance: 300.0) let account3 = Account(name: "Credit Card Account", balance: -100) accounts.append(account1) accounts.append(account2) accounts.append(account3) sleep(3) completion(accounts) }}
Intents Extension
We are ready to add some Siri to our bank app. Let’s add another target (File > New > Target… > Intent Extension) named QuickBalanceIntentsExtension
. Uncheck Include UI Extension checkbox for now and select None for Starting Point. Active scheme if it ask you.
There is not much here yet. Just single file IntentHandler
with single function
override func handler(for intent: INIntent) -> Any
We’ll get back here soon.
As I mention before, Siri operates in many domains (= you may ask any questions), but we only interested in accounts data request. Let’s add INSearchForAccountsIntent
for supported intents (extension’s Info.plist > NSExtension > NSExtensionAttributes > IntentsSupported > add new item INSearchForAccountsIntent)
When Siri recognise your speech as intent to do something, here is where it goes first. So, whenever we receive request, we should handle it and provide response. Let’s create class for this in extension (select extension folder File > New > File… > Swift File) named QuickBalanceRequestHandler
. This class just will call our shared framework and wrap our Account
in something Siri understand as INPaymentAccount
. First add Account
extension to QuickBalanceRequestHandler
file and don’t forget to import Intents
and QuickBalance
framework
import Intentsimport QuickBalanceextension Account { public var intentAccount: INPaymentAccount { return INPaymentAccount(nickname: INSpeakableString(spokenPhrase: self.name), number: nil, accountType: .unknown, organizationName: nil, balance: INBalanceAmount(amount: NSDecimalNumber(floatLiteral: self.balance), currencyCode: "EUR"), secondaryBalance: nil) }}
As you can see INPaymentAccount
has many fields. But almost all are optional and we won’t touch them for now. Now add QuickBalanceRequestHandler
to the end of file:
class QuickBalanceRequestHandler: NSObject, INSearchForAccountsIntentHandling { func handle(intent: INSearchForAccountsIntent, completion: @escaping (INSearchForAccountsIntentResponse) -> Void) { QuickBalanceService().quickBalanceAccounts { (accounts) in let response: INSearchForAccountsIntentResponse guard accounts.count > 0 else { response = INSearchForAccountsIntentResponse(code: .failureAccountNotFound, userActivity: .none) completion(response) return } response = INSearchForAccountsIntentResponse(code: .success, userActivity: .none) response.accounts = accounts.compactMap({ return $0.intentAccount }) completion(response) } }}
Again, not much here. Just trying to fetch account. If no luck, return .failureAccountNotFound
code so Siri may reply properly (check INSearchForAccountsIntentResponseCode
enum for more options). If accounts found, wrap them in INPaymentAccount
and return with .success
code.
Now we are ready to handle Siri’s request with proper response. Navigate to IntentHandler file and replace function with:
override func handler(for intent: INIntent) -> Any? { if intent is INSearchForAccountsIntent { return QuickBalanceRequestHandler() } return .none}
We are ready to talk to Siri. But we won’t. As there might be people around you now. So let’s configure Xcode to do it quietly and launch QuickBalanceIntentsExtension
scheme (Xcode > Product > Scheme > Edit scheme… > Run > Info > Siri Intent Query) with question “How much money I have in MyBank app?
”
And launch QuickBalanceIntentsExtension
scheme. You should see something like this
As you might notice, we specified which app use in question. Try to not specify and initially reply might be like this
Eventually, Siri will use proper app even without specifying it.
That’s all for now. Sources are here. Next, we create custom UI for our accounts list and try to teach Siri to reply to more specific questions about accounts.