Flutter quickstart
One plugin wraps the native iOS and Android SDKs. Add the git dependency, do the two-line platform setup, set credentials, and present.
Install the SDK
The plugin is consumed as a git dependency pinned to a release tag.
dependencies: voqal_flutter: git: url: https://github.com/VoqalAI/voqal-flutter.git ref: "2.1.0"Platform setup
Android needs the Voqal Maven repository (same as the native SDK) and a FlutterFragmentActivity host. iOS needs a 16.0 platform floor and a microphone usage description.
import io.flutter.embedding.android.FlutterFragmentActivity// Must extend FlutterFragmentActivity (a ComponentActivity), NOT FlutterActivity.class MainActivity : FlutterFragmentActivity()# ios/Podfileplatform :ios, '16.0'<!-- ios/Runner/Info.plist --><key>NSMicrophoneUsageDescription</key><string>Voqal uses the microphone for voice conversations.</string>Configure and set credentials
Call setuponce at app start, then hand the SDK your end user’s auth token with setCredentials — refresh it whenever it rotates.
import 'package:voqal_flutter/voqal_flutter.dart';final voqal = Voqal();await voqal.setup(const VoqalConfig( apiKey: 'pk_live_…', // your Voqal API key (required, public) requestId: 'prod-yourtenant', // "prod-" / "stg-" selects the environment theme: VoqalTheme(accent: '#2d5bff', appearance: VoqalAppearance.auto),));// Credentials are set separately and can be refreshed at any time.await voqal.setCredentials( yourAuthToken, metadataJson: '{"country_code":"EGY","user_id":"123"}',);await voqal.prewarm(); // optional: warm the connection so the first turn is instantprewarm opens the engine connection in the background so the assistant answers instantly the first time it opens.Forwarding headers
New in 2.0.0. Give setup a forwardedHeaders provider to send your own headers to your backend on every turn. Each entry reaches the engine namespaced as X-Voqal-Forward-<name>; the engine strips the prefix and forwards the header verbatim to your tenant’s backend. The function is read live on every turn, so a rotating value — a short-lived Authorization, for example — always sends its current value.
await voqal.setup(VoqalConfig( apiKey: 'pk_live_…', requestId: 'prod-yourtenant', // Evaluated fresh on every turn — a rotating token always sends its current value. forwardedHeaders: () => { 'Authorization': 'Bearer $accessToken', 'X-Tenant-Id': tenantId, },));This is a different path from metadata. setCredentials(token, metadataJson: …) sends X-Client-Metadata— Voqal’s own context (country, user id) that the engine reads to route and personalize. Forwarded headers are passed straight through to your backend and never interpreted by Voqal.
X-Token, X-Voqal-Key, X-Request-Id, X-Client-Metadata, Content-Type, Accept) are dropped if returned — they can never override the SDK’s own headers. Authorization is forwarded. At most 32 headers are sent.Redirect & checkout
When the agent surfaces a confirm widget — say, a completed cart — you often want to hand the user back to your own screen to finish. Set actionButtonEnabled: true to render an accent button beside the voice wave, then handle onActionButtonTapped to navigate. The callback carries no payload: the SDK has already dismissed itself, and your app decides where to go.
The button is off by default. Leave actionButtonEnabled out (or set it to false) and nothing is drawn beside the voice wave — right for a product with no checkout, such as a booking or support assistant.
// 1. Turn the action button on in config.await voqal.setup(VoqalConfig( apiKey: 'pk_live_…', actionButtonEnabled: true, // accent button beside the voice wave));// 2. Handle the tap — no payload; you decide where to go.voqal.onActionButtonTapped = () { Navigator.of(context).push( MaterialPageRoute(builder: (_) => const CheckoutPage()), );};Present the assistant
Open the assistant from any widget.
ElevatedButton( onPressed: () => voqal.present(), child: const Text('Talk to Voqal'),)- Voice, transcription, widgets, and confirmations are handled inside the sheet — your app ships no assistant UI.
- Theming, presentation style, header branding, and the action button share one model across platforms — see Configuration.
Your own button
There is no Voqal launcher widget in Flutter. Any tappable thing you already have — a FloatingActionButton, an icon in the app bar, a row in a menu, a deep link — calls voqal.present(). The assistant opens over your screen and manages its own navigation until the user dismisses it.
// A floating action button — the plugin ships no launcher widget; this is yours.Scaffold( floatingActionButton: FloatingActionButton.extended( onPressed: () => voqal.present(), icon: const Icon(Icons.mic), label: const Text('Ask'), ), body: …,)By default it comes up as a near-full sheet the user can swipe down. For an edge-to-edge screen that closes only from its own button, set presentationStyle at setup — it is not a per-call option.
// Setup-time setting: apply before present().await voqal.setup(VoqalConfig( apiKey: 'pk_live_…', presentationStyle: VoqalPresentationStyle.fullScreen, // default: .sheet));voqal.prewarm() once after setCredentials so the connection is already open when the user taps — the first turn lands noticeably faster.