Skip to main content

Flutter SDK changelog

Canonical version history lives in CHANGELOG.md in the drukverify_flutter repo (private; ask your integration contact for access). Highlights of each release below.

2.2.0 — 2026-05-14

Public-API hardening series — eight new primitives for apps that want to build their own capture / verify UIs instead of using the bundled widgets. Internally adopted by EkycBurstFaceVerifyWidget (now delegates to BurstFaceVerifier); no breaking changes to existing callers.

Added — error UX

  • EkycException.userMessage — extension getter that maps the structured type field (expired_token, rate_limited, feature_disabled, etc.) to short end-user-safe English copy. Never leaks the developer-oriented .message field. Drop it straight into a SnackBar:

    try {
    await client.faceVerifyImages(imageA: a, imageB: b);
    } on EkycException catch (e) {
    showSnackBar(e.userMessage);
    }

Added — permissions facade

  • EkycPermissions — one-line camera + photo-library permission requests with throw-on-denial semantics. isCameraPermanentlyDenied() / isPhotoLibraryPermanentlyDenied() distinguish first-denial (re-prompt OK) from permanently denied (Settings only). Photo library is iOS-only — Android 13+'s system photo picker grants implicitly, so requestPhotoLibrary() is a no-op there.

Added — custom camera UI

  • EkycCameraView — builder-style camera primitive. Caller owns the layout via builder: (ctx, preview, capture) => …; the SDK owns permission, camera lifecycle, and JPEG encoding. Fills the gap between EkycBurstFaceVerifyWidget (multi-frame loop with its own UI) and the headless CameraFrameSource. Front/back lens locked at construction. ResolutionPreset from package:camera is re-exported so consumers stay on a single import.

  • EkycIdDocumentCapture — specialized widget for ID-document capture (cards, driver's licenses, passports). Live preview + document-shaped overlay (scrim, rounded outline, corner markers, optional caption) + shutter; crops the captured frame to the overlay rect by default. Built on EkycCameraView + EkycImagePreprocessor — no duplication. IdDocumentAspect.cr80 (1.586:1, default), passport (1.4:1), or custom with customAspectRatio:.

Added — burst face-verify

  • BurstFaceVerifier — headless burst face-verify loop. Same algorithm the widget runs, but usable without mounting any UI. Returns a sealed BurstOutcome (BurstMatched / BurstFailed / BurstCancelled) so callers can switch cleanly. Cooperative cancel() checked after each await. The widget now delegates to this class so there's one source of truth for the loop.

    final verifier = BurstFaceVerifier(client: ekyc, referenceImage: enrolledSelfie);
    switch (await verifier.run(onProgress: (s) => print(s.stage))) {
    case BurstMatched(:final result): handleMatch(result);
    case BurstFailed(:final failure): handleFail(failure);
    case BurstCancelled(): break;
    }
  • BurstFaceVerifier.progressStreamStream<BurstStatus> that fires the same sequence as the onProgress callback. For stream-based state management (Riverpod StreamProvider, BLoC, StreamBuilder). Broadcast — many subscribers, no replay buffer. Closes when run returns.

Added — on-device pre-checks + preprocessing

  • EkycFaceQualityCheck — on-device face quality gate that screens frames before paying for face.verify. Issues surface as a stable enum (noFace, multipleFaces, faceTooSmall, faceNotForward, eyesClosed); thresholds tunable per call. Conservative defaults — unknown angles count as forward, unknown eye probabilities count as open (fail-open). EkycFaceDetector exposed separately for advanced callers wanting raw ML Kit Face data. ML Kit Face / FaceLandmark types re-exported from the top-level library.

  • EkycImagePreprocessor — pure-Dart image preprocessing helpers: resize (defaults to maxLongEdge 1024), crop (with paddingRatio, clamped to bounds), cropToFace (takes ML Kit Face), reencodeJpeg. Synchronous internally but exposed as Future so callers can swap to compute() for off-main-thread without breaking the API. Throws ArgumentError on non-image input.

Changed

  • EkycBurstFaceVerifyWidget now delegates its internal burst loop to BurstFaceVerifier. Public API + observable behaviour are unchanged — existing callers continue to work without modification.

Dependencies

  • New direct dep: image ^4.2.0 (pure-Dart codec).
  • New direct dep: meta ^1.10.0 (was transitive; needed for @visibleForTesting now used in lib/).

See Custom UI primitives for usage walkthroughs of each new API.

2.1.3 — 2026-05-13

Fixed

  • EkycLivenessWidget now renders the live camera preview behind its challenge overlay so users can see themselves while aligning with the face oval and following on-screen prompts ("blink", "turn left", "smile"). Earlier 2.x versions composited the overlay on a black background — the camera ran and fed frames to ML Kit, but preview frames were never displayed. Materially hurt completion rates. Front-camera mirroring is handled by Flutter's CameraPreview natively on Android and iOS.

Added

  • CameraFacade.buildPreview() on the test-seam interface. Production DefaultCameraFacade returns a CameraPreview; test fakes can return SizedBox.shrink().
  • LivenessController.camera getter exposes the facade so integrators building custom liveness UIs can render the preview themselves.

2.1.2 — 2026-05-13

Fixed

  • EkycClient.faceVerifyImages, ocrScanImages, and livenessCheckImage now throw a typed EkycException directly on 4xx / 5xx — matching the contract documented in ErrorInterceptor ("callers can use on EkycRateLimitException catch (e)"). Earlier 2.x versions leaked Dio's envelope: the typed exception was attached as DioException.error and callers had to pattern-match on DioException catch (e) { if (e.error is EkycException) }. v2.1.2 wraps each public boundary in _ekycCall<T> which strips the DioException envelope when the inner cause is an EkycException. Non-EkycException DioExceptions (timeouts, DNS failures) still propagate as DioException.

2.1.1 — 2026-05-13

Fixed

  • EkycConfig.apiVersion default changed from '2026-05-12' to '2026-04-26' to match the gateway's SUPPORTED_VERSIONS. The previous default was forward-dated from the 2.0.0 cut and caused unknown_version (HTTP 400) on every endpoint that goes through the gateway's version middleware — i.e. all of OCR, face, and liveness. /v1/ping was unaffected because it bypasses that middleware, which is why the bug wasn't caught at the 2.1.0 ship gate. All 2.0.x and 2.1.0 consumers should upgrade.

2.1.0 — 2026-05-13

Added

  • EkycClient.faceVerifyImages({required File imageA, required File imageB}) — hand-rolled multipart wrapper for /v1/face/verify. The OpenAPI spec doesn't describe the image_a / image_b fields, so the codegen'd client.face.faceVerify() sends no body. Use this helper.
  • EkycClient.ocrScanImages({required File image, File? backImage}) — same shape for /v1/ocr/scan. Optional backImage for two-sided IDs.

Both mirror the existing livenessCheckImage pattern.

2.0.1 — 2026-05-13

Patch release. No code changes.

  • Repository renamed on GitHub: Cloud-Bhutan/ekyc-sdk-flutterDrukVerify/drukverify-flutter. The previous URL still redirects; pubspec.yaml metadata (homepage, repository, issue_tracker) now points at the new canonical URL.
  • Brand-aligned typedef aliases added: DrukVerifyClient, DrukVerifyConfig, DrukVerifyException and subclasses all map 1:1 to the Ekyc* canonical types. Use whichever reads better in your codebase.

2.0.0 — 2026-05-13

Renamed from ekyc_sdk to drukverify_flutter as part of the 2.0 release. Update your pubspec.yaml dependency name.

Breaking changes. Rewires the SDK against the multi-tenant ekyc-platform. The 1.x line is incompatible.

Migration from 1.x

  1. Package rename:

    # 1.x
    ekyc_sdk: ^1.x
    # 2.0+
    drukverify_flutter:
    git:
    url: git@github.com:DrukVerify/drukverify-flutter.git
    ref: v2.2.0
  2. EkycSdkEkycClient; EkycConfig.baseUrl:host:; the static-key constructor is replaced by apiKey: + tokenProvider::

    // 1.x
    final ekyc = EkycSdk(apiKey: 'static-key');

    // 2.0+
    final ekyc = EkycClient(config: EkycConfig(
    apiKey: 'pk_live_xxx', // public key, embeddable in app
    tokenProvider: () async { // your server mints session tokens
    final res = await http.post('https://your-server.com/ekyc/token');
    return jsonDecode(res.body)['token'];
    },
    ));
  3. Method renames:

    • face.verify(imageA:, imageB:)faceVerifyImages(imageA:, imageB:)
    • ocr.scan(image:, backImage:)ocrScanImages(image:, backImage:)
    • liveness.check(mode:, frames:)livenessCheckImage(File)
  4. LivenessMode enum is removed — EkycLivenessWidget configures challenges via LivenessConfig.allowedChallenges instead. The widget's sdk: prop is now client:, and the mode: and onError: props are gone (errors arrive via LivenessResult.error in onResult).

  5. Result types are renamed to match the OpenAPI generator output:

    • FaceVerifyResultFaceVerify200Response
    • OcrResultOcrList200ResponseDataInner
    • LivenessCheckResultLivenessCheck200Response (raw) / LivenessResult (widget)
  6. EkycErrorEkycException; .code.type; .param field removed.

Added

  • EkycClient facade with multipart upload helpers + generated sub-API accessors (client.face, client.ocr, client.liveness, client.sessions, client.diagnostics)
  • tokenProvider async callback with single-flight reactive 401 refresh
  • EkycLivenessWidget with on-device ML Kit face detection + challenge gating
  • Generated DTOs from the platform's OpenAPI snapshot via dart-dio
  • Auto-injection of Drukverify-Version, Idempotency-Key, X-Request-Id headers

Removed

  • apiKey-only auth (replaced by tokenProvider flow)
  • enableOnDeviceLiveness toggle (on-device is always on, since it's coupled to challenge UX)
  • Hand-rolled HTTP client (replaced by dio ^5.7)
  • Fraud, Batch, Verification namespaces (deferred to SP5 / SP8)