Skip to content

Implementation ​

This guide uses Flutter SDK 0.4.0. See installation and update instructions.

Create AudioRecognition instance ​

Create a SORIAudioRecognizer with your SORI application credentials. Do not hardcode real credentials in public source code. Load them through your release configuration, secure storage, or environment-specific build process.

The example below uses --dart-define values so the source code can stay free of sensitive values:

dart
import 'package:sorisdk_flutter/sorisdk_flutter.dart';

const applicationId = String.fromEnvironment('SORI_APP_ID');
const secretKey = String.fromEnvironment('SORI_SECRET_KEY');

final recognizer = SORIAudioRecognizer(
  applicationId: applicationId,
  secretKey: secretKey,
);

await recognizer.configure();

Run your app with values provided by your local or CI environment:

bash
flutter run \
  --dart-define=SORI_APP_ID=your-application-id \
  --dart-define=SORI_SECRET_KEY=your-secret-key

WARNING

app_id and secret_key authenticate your app with the SORI API Server. Do not commit real values to a repository, sample app, issue, screenshot, or public documentation.

Web recognizer ​

Browser builds must not contain the long-lived SORI secret_key. After preparing the Web bridge during installation, create the recognizer with an ephemeral-key endpoint on your application server:

dart
final recognizer = SORIAudioRecognizer.web(
  applicationId: applicationId,
  webAuth: SORIWebAuthOptions.ephemeralKeyEndpoint(
    Uri.parse('/api/sori/ephemeral-key'),
    requestCredentials: SORIWebRequestCredentials.sameOrigin,
  ),
);

await recognizer.configure();

Protect the endpoint with the same session, origin, CSRF, and rate-limit controls as your other application APIs. The long-lived secret_key stays on the server, while applicationId is compiled into the browser application and is not secret. The endpoint returns only the short-lived ephemeral key to the browser. See Ephemeral Key.

Listen for recognition events ​

Subscribe to recognizer.events before starting recognition. Campaign information is available from event.campaign or from the event payload depending on the native platform event.

dart
import 'dart:async';

import 'package:flutter/foundation.dart' show kIsWeb;

import 'package:sorisdk_flutter/sorisdk_flutter.dart';

late final SORIAudioRecognizer recognizer;
StreamSubscription<SORIRecognitionEvent>? subscription;

subscription = recognizer.events.listen((event) {
  if (event.type == SORIRecognitionEventType.stateChanged) {
    final state = event.payload['state'];
    // Update your UI for STARTING, STARTED, or stopped states.
  }

  final campaign = event.campaign;
  if (campaign != null) {
    // Render campaign.name, campaign.imageUrl, and campaign.actionUrl.
  }

  if (event.type == SORIRecognitionEventType.audioMarkerStateChanged) {
    final state = event.audioMarkerState;
    // Update marker-specific UI or state.
  }

  if (kIsWeb && event.type == SORIRecognitionEventType.audioMarkerFound) {
    final marker = event.audioMarkerIdentity;
    // Render the Console-resolved marker ID and name on Web.
    // Native current-marker UI uses audioMarkerStateChanged above.
  }

  if (event.type == SORIRecognitionEventType.error ||
      event.type == SORIRecognitionEventType.networkError) {
    // Show event.message in your app's error UI.
  }
});

Reconcile the current activity segment ​

The following guidance is for existing campaign callbacks. For the new SDK-accepted activity results, use the exact-ID update rules in Audio Marker state and activity results; these results can update the matching historical activity.

Campaign and recognition events expose an optional event.activityId, while a campaign can expose event.campaign?.materialId and event.campaign?.trait?.marker. Show the first campaign event immediately and keep an explicit current segment in application state.

  • A later event with the same non-empty event.activityId may refine only the current row. Do not search older rows, and do not derive activity identity from campaign or material IDs.
  • Older servers can return a null activity ID. A material fallback is safe only when the current row has the same non-empty event.campaign?.materialId.
  • Preserve the current row's first-observation time, position, and other UI metadata. Native and Web reporters keep their transport metadata anchored to the first observation; the application should not replace it with refinement callback time.
  • Keep the first marker and refined campaign fields when a later event contains no marker or less complete campaign data.
  • A campaignFound or recognitionResult event for a different material, or an explicit recognition stop, seals the segment. Ignore delayed refinements for an older activity after that point. audioMarkerFound does not seal a segment.
  • SORIRecognitionEventType.audioMarkerFound reports independent marker discovery. It does not create or refine an activity row by itself. Read the Console-resolved ID and name from event.audioMarkerIdentity.

A(activity-1) -> A'(activity-1, marker) -> A(activity-1, marker miss) is one sticky row. A(activity-1) -> B(activity-2) -> delayed A'(activity-1) keeps B current and leaves the historical A unchanged.

Cancel the subscription when your widget or application scope is disposed:

dart
await subscription?.cancel();

Start and stop recognition ​

Call startRecognition() after the recognizer is configured and your UI is ready to receive events.

dart
await recognizer.startRecognition(
  notification: const SORIAndroidNotificationOptions(
    title: 'SORI recognition',
    body: 'Listening for SORI audio signals',
  ),
);

The notification option is used by Android foreground-service notifications. It is ignored on iOS and Web. On Web, call startRecognition() from a user action so the browser can request microphone permission.

To stop recognition:

dart
await recognizer.stopRecognition();

You can also check the current recorder state:

dart
final isRunning = await recognizer.isRecorderRunning();

Enable Audio Marker recognition ​

Audio Marker recognition is disabled by default. Enable it only when your SORI Console setup uses Audio Markers.

dart
await recognizer.configure(
  config: const SORIRecognitionConfig(audiomarker: true),
);

For current-marker UI and later activity enrichment, use the SDK-managed state and activity result APIs. The SDK manages expiry, marker traits and updates to the same activity. Existing campaign and discovery callbacks remain compatible.

Flutter Web retains its existing recognition and event behavior. The new native current-state, expiry and independent late-marker update behavior applies to Android/iOS. getCurrentAudioMarkerState() returns null on Web; do not use it as proof that no marker was detected.

Handling campaign actions ​

When a campaign has an action URL, decide in your app whether the URL should be opened. After your app accepts the URL, call handleActionUrl() to report and handle the interaction through the SDK.

dart
final actionUrl = campaign.actionUrl;
if (actionUrl != null && actionUrl.isNotEmpty) {
  await recognizer.handleActionUrl(actionUrl);
}

Updating the recognition database ​

The SDK can ask the native recognizer to update its local recognition database without interrupting recognition. Android returns the refresh result. On iOS, the refresh is requested but the native API does not expose completion, so success: false with the explanatory message means that the outcome is unavailable, not that the refresh failed.

The four-field SORIUpdateResult API is preserved. updateDatabase() is not currently supported on Web.

dart
final result = await recognizer.updateDatabase();

if (!result.success) {
  // Android: handle an actual failure reported in result.errorMessage.
  // iOS: the message explains that the refresh outcome is unavailable.
}