Integration Guide

A reference architecture for building a verifier app using the Verifier Flutter SDK. This guide shows which screens your app owns, which the SDK renders, and how they connect.

Thin Client Pattern

The verifier app is intentionally thin. Your app handles navigation, authentication, and VIN acquisition. The SDK handles all verification UI, NFC/BLE communication, and backend polling. Most screens are just wrappers around an SDK widget with navigation callbacks.

App Architecture

MaterialApp
├── Auth (your auth layer)
└── MaterialApp.routes
    ├── HomeScreen                ← App (entry point)
    ├── CameraScanScreen          ← App (VIN acquisition)
    ├── ManualEntryScreen         ← App (VIN acquisition)
    └── VerificationScreen        ← App wrapper → SDK VerificationView

Screen Responsibilities

Screens Your App Owns (100% app code)

These screens have no SDK widgets — your app renders all UI and handles all logic.

ScreenPurposeNavigates To
HomeScreenAction cards for different verification methodsCameraScan, ManualEntry
CameraScanScreenLive camera OCR to detect VIN from vehicle dashboardVerification (with detected VIN)
ManualEntryScreen17-character VIN text input with validationVerification (with entered VIN)

Screens That Wrap SDK Widgets

These screens are thin wrappers — typically under 80 lines. Your app provides a Scaffold, creates the SDK instance, and wires up navigation callbacks. The SDK renders all visible UI.

ScreenSDK WidgetWhat the SDK Renders
VerificationScreenVerificationViewQR code, countdown timer, NFC delivery badge, and built-in result states. VerificationController for custom UI.

SDK Lifecycle

The Flutter SDK uses a factory function rather than a provider. Each screen creates an SDK instance when it mounts and destroys it when it unmounts:

import 'package:vecu_verifier_flutter_sdk/vecu_verifier_flutter_sdk.dart';

class VerificationScreen extends StatefulWidget {
  @override
  State<VerificationScreen> createState() => _VerificationScreenState();
}

class _VerificationScreenState extends State<VerificationScreen> {
  late final VerifierSDK _sdk;

  @override
  void initState() {
    super.initState();
    _sdk = createVerifierSDK(VerifierConfig(
      stage: DeploymentStage.production,
      bearerToken: context.read<Auth>().token,
    ));
  }

  @override
  void dispose() {
    _sdk.destroy();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => Scaffold(
    body: VerificationView(sdk: _sdk, vin: widget.vin, /* callbacks */),
  );
}

If your app prefers a shared instance, create the SDK in a parent widget or use your app's DI solution (Riverpod, get_it, Provider) and pass it down. The SDK object is cheap — creating it per-screen is fine for most apps.

Writing a Wrapper Screen

A typical wrapper screen follows this pattern:

class VerificationScreen extends StatefulWidget {
  final String vin;
  const VerificationScreen({required this.vin, super.key});

  @override
  State<VerificationScreen> createState() => _VerificationScreenState();
}

class _VerificationScreenState extends State<VerificationScreen> {
  late final VerifierSDK _sdk;

  @override
  void initState() {
    super.initState();
    _sdk = createVerifierSDK(_config);
  }

  @override
  void dispose() {
    _sdk.destroy();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Verify Credentials')),
      body: VerificationView(
        sdk: _sdk,
        vin: widget.vin,
        proximity: const ProximityConfig(thresholdMiles: 5.0),
        // SDK says "what happened", app decides "where to go"
        onApproved: (result) {
          Navigator.pushReplacementNamed(context, '/result', arguments: {
            'outcome': 'approved',
            'vin': result.verifiedClaims?.vin,
          });
        },
        onDenied: (result) {
          Navigator.pushReplacementNamed(context, '/result',
              arguments: {'outcome': 'denied'});
        },
        onExpired: (_) => Navigator.pop(context),
        onError: (error) => ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text(error.userMessage)),
        ),
      ),
    );
  }
}

The SDK has zero knowledge of your navigation structure. It communicates via callbacks — your app decides where to navigate.

Callback-Based Integration

For screen-based integrations, use the callback props as your app boundary:

  • onApproved
  • onDenied
  • onExpired
  • onError

These are plain functions that your wrapper screen passes into VerificationView. Internally, the SDK listens to its own event emitter and then invokes those callbacks for you.

Verification Flow

HomeScreen
├── CameraScanScreen ──► VIN ──► VerificationScreen (SDK: QR + polling)
└── ManualEntryScreen ──► VIN ──► VerificationScreen

Your app's job: Acquire the VIN (camera, keyboard, or your own source). SDK's job: Create presentation request, display QR, poll for result.

What Your App Does NOT Need to Manage

The SDK handles all of the following internally:

  • Presentation request creation and QR code generation
  • Verification status polling and timeout
  • All in-flow UI states (loading, QR, waiting, approved, denied, expired, error)
  • Optional NFC delivery of the request via Android HCE (see Permissions)

Next Steps

  • Overview — Quick start with VerificationView
  • Permissions — Native setup for NFC delivery and entitlements
  • API Reference — Full SDK configuration, widgets, events, and types