Verifier Flutter SDK

Dart/Flutter SDK for verifying driver custody credentials via QR code-based OID4VP flows on iOS and Android.

Stable Release

Currently v0.1.2.

Quick Start

1. Install

Add to your pubspec.yaml:

dependencies:
  vecu_verifier_flutter_sdk:
    git:
      url: https://ghe.coxautoinc.com/VehicleCustody/vecu-verifier-flutter-sdk.git
      ref: v0.1.2

Then run:

flutter pub get

2. Widget-Based (Recommended)

Use VerificationView for the full verification flow with built-in UI:

import 'package:vecu_verifier_flutter_sdk/vecu_verifier_flutter_sdk.dart';

final sdk = createVerifierSDK(VerifierConfig(
  stage: DeploymentStage.sandbox,
  bearerToken: 'your-vecu-api-token',
));

class VerifyScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return VerificationView(
      sdk: sdk,
      vin: '1HGBH41JXMN109186',
      proximity: const ProximityConfig(thresholdMiles: 5.0),
      onApproved: (result) {
        print('Verified! ${result.requestId}');
        print('Proximity: ${result.proximityCheck?.status}');
      },
      onDenied: (result) {
        print('DO NOT RELEASE ${result.requestId}');
      },
      onExpired: (result) {
        print('Session expired');
      },
      onError: (error) {
        print('${error.code}: ${error.message}');
      },
    );
  }
}

VerificationView handles all states: loading, QR code display with countdown timer, approved (with proximity pass/fail/unchecked), denied, expired, and error.

3. Headless (Custom UI)

Use VerificationController for full control over the UI while the controller manages the verification flow (API calls, polling, state transitions):

final sdk = createVerifierSDK(VerifierConfig(
  stage: DeploymentStage.sandbox,
  bearerToken: 'your-vecu-api-token',
));

final controller = VerificationController(
  sdk: sdk,
  vin: '1HGBH41JXMN109186',
  proximity: const ProximityConfig(thresholdMiles: 5.0),
  onApproved: (result) => print('Verified! ${result.requestId}'),
  onDenied: (result) => print('DO NOT RELEASE'),
  onExpired: (_) => print('Expired'),
  onError: (error) => print('Error: ${error.displayCode}'),
);

Build your UI with ListenableBuilder and pattern matching on controller.state:

ListenableBuilder(
  listenable: controller,
  builder: (context, _) {
    return switch (controller.state) {
      LoadingState() => const CircularProgressIndicator(),
      QRState(:final qrContent, :final expiresAt) =>
        MyQRWidget(qrContent, expiresAt),
      ApprovedState(:final result) => MyApprovedWidget(result),
      DeniedState(:final result, :final vin) => MyDeniedWidget(result, vin),
      ExpiredState() => const Text('Session expired'),
      QRExpiredState() => TextButton(
          onPressed: controller.restart,
          child: const Text('QR Expired — Retry'),
        ),
      ErrorState(:final error) => Text('Error: ${error.userMessage}'),
    };
  },
)

Clean up when done:

controller.dispose();
sdk.destroy();

Headless Example

See the examples/headless/ directory for a complete working example with dark-themed custom UI, event log, and mock controls.

4. Low-Level SDK (Advanced)

For maximum control, use the core SDK methods directly:

final sdk = createVerifierSDK(VerifierConfig(
  stage: DeploymentStage.sandbox,
  bearerToken: 'your-vecu-api-token',
));

final result = await sdk.createPresentationRequest('1HGBH41JXMN109186');
if (result is ApiSuccess<CreatePresentationResponse>) {
  // Render result.data.qrContent as a QR code
  sdk.startPolling();
}

sdk.on<StatusChangedEvent>((event) {
  print(event.currentStatus.status); // pending, approved, denied, expired
});

// Clean up when done
sdk.destroy();

Requirements

RequirementVersion
Flutter>= 3.16.0
Dart>= 3.2.0, < 4.0.0

Next Steps

  • API Reference — Configuration, widgets, methods, events, types