Graine AI

Calling reference

Every method, option, field, state, outcome and push key for in-app calls, side by side for iOS, Android, React Native and Flutter — with the limits, the error messages and what each platform does differently.

Versions

PlatformPackageCalls fromRequires
iOSGraineInApp (Swift package, https://github.com/GraineAI/inapp-ios)0.6.0iOS 15
Androidai.graine:inapp-android0.6.0Android 7 (API 24)
React Native@graineai/inapp-react-native-calls0.1.0React Native 0.71, a native build. Screen context and actions need @graineai/inapp-react-native 0.40.1
Fluttergraine_inapp0.6.1Flutter 3.19
Calls over WebRTC (optional)iOS GraineInAppRTC (https://github.com/GraineAI/inapp-ios-rtc); Android ai.graine:inapp-android-rtc; React Native calls 0.4.0 and Flutter built with WebRTC0.9.0The same version as the SDK it sits beside — Calls over WebRTC

The latest are iOS, Android and Flutter 0.7.0, React Native 0.41.0 and its calls package 0.2.0 — the versions Graine's own ringing (hosted ringing) and several agents in one app need. What each changed is in the changelog.

Configuration

iOS — GraineCallsConfig

Passed to GraineInApp.shared.calls.configure(_:). You can call configure again; the change applies to the next ring.

OptionTypeDefault
displayNameString"": your app's nameWhat the ring and the call screen show. A push's caller_name replaces it for that call
ringtoneSoundString?nil: the system ringtoneA sound file in your app's bundle
iconTemplateImageDataData?nilA PNG template image, 40 × 40 points on a transparent background, for your app's button on the system call screen
includesCallsInRecentsBoolfalseWhether calls appear in the phone's Recents
ringTimeoutTimeInterval30Seconds before an unanswered ring is missed; at least 1
callTransportGraineCallTransport.autoWhat carries the calls' audio: .auto (WebRTC when GraineInAppRTC is in the app), .websocket or .webrtc. 0.9.0

Android — GraineCallsConfig

Passed to GraineInApp.get().calls.configure(config), in Application.onCreate.

OptionTypeDefault
displayNameString"Assistant"What the ring and the call screen show. A push's caller_name replaces it for that call
smallIconInt (a drawable)0: the platform's call iconThe monochrome status-bar icon
ringtoneUri?null: the phone's ringtone
callActivityClass<out Activity>?null: GraineCallActivityYour own call screen — see the call screen
ringTimeoutMsLong30_000Milliseconds before an unanswered ring is missed
callTransportGraineCallTransportAUTOWhat carries the calls' audio: AUTO (WebRTC when ai.graine:inapp-android-rtc is in the app), WEBSOCKET or WEBRTC. 0.9.0

React Native and Flutter

GraineCalls.configure(options) in React Native, and GraineCalls.instance.configure(GraineCallsOptions(...)) in Flutter. The options are kept on the phone, so a push that wakes a closed app rings before any JavaScript or Dart has run. A new publishableKey or baseUrl takes effect at the app's next launch.

React NativeFlutterDefault
publishableKeypublishableKeyRequiredThe agent's publishable key (Agent → Embed & Widgets)
displayNamedisplayNameRequiredWhat the ring and the call screen show
baseUrlbaseUrlhttps://www.graine.ai
ringtoneSoundringtoneSoundThe phone's ringtoneiOS: a sound file in your bundle. Android: a raw resource — ring and sounds/ring.caf both mean res/raw/ring; an unknown name logs a warning and uses the phone's ringtone
ringTimeoutMsringTimeout (a Duration)30 seconds
appVersionappVersionLands on the call record
callScreencallScreen"sdk" (GraineCallScreen.sdk)Who draws the call while your app is open: the SDK's screen, or "app" for your own
android.smallIconandroidSmallIconThe platform's call iconA drawable's name; an unknown name logs a warning
ios.iconTemplateiosIconTemplateAn image's name in your asset catalogue
ios.includesCallsInRecentsiosIncludesCallsInRecentsfalse
debugdebugfalseThe native SDK's debug log for calls — see Logging
callTransportcallTransport (GraineCallTransport)"auto" (GraineCallTransport.auto)What carries the calls' audio: "auto" (WebRTC in a build with WebRTC), "websocket" or "webrtc" — exact, case-sensitive words. Kept for cold starts only when it is not auto. React Native calls 0.4.0, Flutter 0.9.0

A wrong value fails configure with the code invalid_options and a message naming the option — for example callScreen must be "sdk" or "app". Unknown keys are ignored.

callScreen: "app" — on iOS the SDK presents nothing while your app is open, and a locked iPhone still shows the system's call screen. On Android your app's launch activity draws the call; if it cannot be found, the SDK uses its own screen and logs why.

Methods

iOS — GraineInApp.shared.callsAndroid — GraineInApp.get().callsReact Native — GraineCallsFlutter — GraineCalls.instance
Configureconfigure(_:)configure(config)configure(options)configure(options)
Receive VoIP pushesenableVoIPPushes(onToken:)—enableVoIPPushes(onToken)enableVoIPPushes(onToken)
Hand over a pushAutomatic; your own registry: reportIncomingCall(payload:completion:)Automatic with Firebase in the app; from a messaging service of your own: handlePush(data)Automatic with Firebase in the app; from a messaging service of your own: handlePush(data)Automatic with Firebase in the app; from a messaging service of your own: handlePush(data)
Hand over the push token, for hosted ringingAutomatic with enableVoIPPushes()Automatic with Firebase in the app; from a messaging service of your own: GraineCalls.onNewToken(context, token)Automatic with Firebase in the app; from a messaging service of your own: registerPushToken({ token })Automatic with Firebase in the app; from a messaging service of your own: registerPushToken(token)
Place a callstartCall(reason:variables:)startCall(activity, reason, variables)startCall({ reason, variables })startCall(reason:, variables:)
Answeranswer()answer()answer()answer()
Declineend() while ringingdecline()decline()decline()
Hang upend()end()end()end()
MutesetMuted(_:)setMuted(value)setMuted(muted)setMuted(muted)
LoudspeakersetSpeaker(_:)setSpeaker(on)setSpeaker(on)setSpeaker(on)
OutcomesGraineCallsListener.onCallOutcome(_:callId:)GraineCallListener.onCallOutcome(callId, outcome)onOutcome(fn)outcomes (a stream)
RingsonIncomingCall(_:)onIncomingCall(call)onIncomingCall(fn)incomingCalls (a stream)
The call, to drawPublished propertiesProperties and GraineCallListeneruseGraineCall(), getSnapshot(), subscribe(fn)value — a ValueListenable<GraineCallSnapshot>
Screen context and actionsAutomaticAutomaticattach(client)attach()
Permission results—onRequestPermissionsResult(code, results), from your own call screenAutomaticAutomatic
Ask for the call permissionsrequestCallPermissions() (async) or requestCallPermissions(completion:)requestCallPermissions(activity) { p -> }requestCallPermissions()requestCallPermissions()
Read themcallPermissionscallPermissionscallPermissions()callPermissions()
Open SettingsopenCallSettings(_:)openCallSettings(item)openCallSettings(item)openCallSettings(item)
What the setup lackscallSetupProblemscallSetupProblemscallSetupProblems()callSetupProblems()
Platform check——isAvailableisSupported
  • startCall returns the call's id, or nothing while another call is live. On Android it must be called on the main thread.
  • handlePush (Android, and the Android side of React Native and Flutter) is what the SDK's own messaging service calls for every message; an app with a messaging service of its own, which receives every message instead, calls it first. It returns false for a push that is not a call, and true for any call push: it rings, or is reported missed during another call, and one without a call_id is dropped with a warning. On iOS it always returns false: VoIP pushes reach the SDK directly.
  • enableVoIPPushes delivers the token as hex, at once if it is already known, and nil/null when iOS invalidates it. In React Native and Flutter it returns a function that stops the updates.
  • attach (React Native, Flutter) hands a call who the customer is, your prompt variables, the current screen and your actions, and keeps them in step. It returns a function that detaches. Without it, the agent's action requests during a call are refused. In React Native with @graineai/inapp-react-native 0.40.2 or later, it also ends a conversation in the assistant bar when a call goes live, and keeps the bar from starting until the call is over.
  • Listeners are called on the main thread. iOS holds them weakly; Android holds them until you remove them.
  • Before your listener exists — an app launched by a push, say — React Native and Flutter keep the last 20 outcomes and rings and deliver them when you subscribe.

The call

iOSAndroidReact NativeFlutter
Statestatestatestatestatus
Call idcallIdcall?.callIdcallIdcallId
Directiondirectioncall?.directiondirectiondirection
Reason—call?.reasonreasonreason
Name showncallerNamecall?.callerNamecallerNamecallerName
Mutedmutedmutedmutedmuted
LoudspeakerspeakerOnspeakerspeakerOnspeakerOn
The agent is speakingagentSpeakingagentSpeakingagentSpeakingagentSpeaking
The line being saidcaptioncaptioncaptioncaption
Connected atconnectedAt (Date)connectedAt (SystemClock.elapsedRealtime() ms)connectedAt (ms since the epoch)connectedAt (DateTime)
Why it could not connectIn the failed outcomeIn the failed outcomeerrorerror
What carries the audiotransport (GraineCallTransport?)transport ("webrtc" / "websocket")transporttransport (GraineCallTransport?)
Why a call that tried WebRTC is on the WebSockettransportFallbackReasontransportFallbackReasontransportFallbackReasontransportFallbackReason

Call ids are your server's call_id for a call it rang. A call the customer places gets a generated one — call_ and a lowercase UUID, on every platform. Treat ids as opaque strings.

States

StateiOSAndroidReact NativeFlutterMeans
Idle.idleIDLE"idle"GraineCallStatus.idleNo call
Ringing.ringingRINGING"ringing".ringingAn agent's call is ringing (incoming only)
Connecting.connectingCONNECTING"connecting".connectingAnswered or placed; the line is opening
Active.activeACTIVE"active".activeThe agent and the customer can hear each other
Ended.ended(reason)ENDED"ended".endedOver

On iOS, ended lasts one second before idle, so a call screen can show how the call ended. On Android, ended passes at once; React Native and Flutter can go from ringing, connecting or active straight to idle. Treat ended and idle alike, and take how the call ended from its outcome. On iOS a state change arrives before its outcome; on Android, after — don't depend on the order.

Outcomes

OutcomeReported whenWith
answeredThe customer answered an incoming call. ended or failed follows
declinedThe customer declined, or hung up while it rang
missedThe ring timed out; or it arrived during another call. iOS also: Do Not Disturb, a Focus or the block list stopped the ring, or the system reset the call
endedA call that connected is over (on Android, also when the customer answers a phone call during a hold)local (the customer hung up), remote (the agent did) or failed (the line broke or, on Android, the microphone stopped working — with a message)
failedThe call never connectedA message saying why

failed messages, for your logs — don't match on them, they may be reworded:

iOSAndroid
MicrophoneThe microphone permission was not granted.The microphone permission was denied. · …was not granted. (no answer to the prompt within 60 seconds) · The microphone could not start: …
Placing the callThe call could not be placed: …The call could not start: … · The call's foreground service could not start: …
The agentThe agent's configuration could not be loaded.The agent could not be loaded.
The lineThe conversation did not open within 15 seconds. · The conversation was refused (status). · The line closed before the call connected.The call could not connect within 15 seconds. · The call was refused (close …). · The call could not be connected.
The pushThe push was not an agent call this SDK can ring.—

The push

Key
typeRequiredagent_call, exactly
call_idRequiredYour id for the call; every outcome carries it back. A push without one never rings
reasonOptionalBecomes call_reason
caller_nameOptionalWhat the ring shows, instead of displayName
variablesOptionalA JSON object, as a string: more prompt variables. Strings, numbers and booleans are kept; anything else is dropped, and if the string cannot be read at all, it is ignored and the call still rings

Values other than type are trimmed, and an empty one counts as absent. Every value must be a string: FCM requires it, and React Native and Flutter turn other values into strings before handing them over. On iOS, a VoIP push that is not an agent call is still reported to CallKit and ended at once — iOS requires it — and reported failed if it carries a call_id.

Prompt variables

VariableValue
who_calledagent for an answered push, customer for startCall. Always set; a variable of the same name cannot replace it
call_reasonThe push's or startCall's reason, when there is one. It replaces a call_reason variable
The call's own variablesFrom the push's variables or startCall's variables. They replace your app's variables of the same name
Your app's variablesWhat your app set (setVariables), read when the call starts
  • Masking. The call's own variables are masked on the phone before they reach the agent — card numbers, email addresses, phone numbers and other personal numbers — as screen context is. The reason is sent as it is.
  • Limits. A call carries up to 12 variables in all, who_called and call_reason included, with names up to 40 characters, values up to 200 characters, and about 1 KB together. The call's own come first; your app's are added while they fit, and the rest are left off — keep to the limits so every variable reaches the agent.

Limits and timeouts

Calls at onceOne per device
Ring30 seconds by default (ringTimeout, ringTimeoutMs); at least 1 second on iOS
Opening the line15 seconds, then failed
Setting a call up over WebRTC12 seconds in all, then the call continues on the WebSocket inside the same call
A dropped connectionThe call ends (ended, failed); it does not move to a new connection. Over WebRTC it gets one chance to recover first, up to about 10 seconds
An action's answer6 seconds, then the agent hears that the action did not finish in time
The microphone prompt during a callAndroid: 60 seconds, then failed. iOS: 60 seconds after Answer, then failed; a call the customer places waits for their answer
Outcomes kept before a listener exists20 (React Native, Flutter)

Logging

PlatformHowWhere
iOSGraineConfig(debug: true)The Xcode console, subsystem ai.graine.inapp
AndroidGraineConfig(debug = true)logcat, tag Graine
React Native, Flutterdebug: true in the calls optionsThe Xcode console and logcat

Warnings and errors are always logged. Audio, the conversation's messages and variable values are never logged.

On this page