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 structuredtypefield (expired_token,rate_limited,feature_disabled, etc.) to short end-user-safe English copy. Never leaks the developer-oriented.messagefield. Drop it straight into aSnackBar: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, sorequestPhotoLibrary()is a no-op there.
Added — custom camera UI
-
EkycCameraView— builder-style camera primitive. Caller owns the layout viabuilder: (ctx, preview, capture) => …; the SDK owns permission, camera lifecycle, and JPEG encoding. Fills the gap betweenEkycBurstFaceVerifyWidget(multi-frame loop with its own UI) and the headlessCameraFrameSource. Front/back lens locked at construction.ResolutionPresetfrompackage:camerais 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 onEkycCameraView+EkycImagePreprocessor— no duplication.IdDocumentAspect.cr80(1.586:1, default),passport(1.4:1), orcustomwithcustomAspectRatio:.
Added — burst face-verify
-
BurstFaceVerifier— headless burst face-verify loop. Same algorithm the widget runs, but usable without mounting any UI. Returns a sealedBurstOutcome(BurstMatched/BurstFailed/BurstCancelled) so callers canswitchcleanly. Cooperativecancel()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.progressStream—Stream<BurstStatus>that fires the same sequence as theonProgresscallback. For stream-based state management (RiverpodStreamProvider, BLoC,StreamBuilder). Broadcast — many subscribers, no replay buffer. Closes whenrunreturns.
Added — on-device pre-checks + preprocessing
-
EkycFaceQualityCheck— on-device face quality gate that screens frames before paying forface.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).EkycFaceDetectorexposed separately for advanced callers wanting raw ML KitFacedata. ML KitFace/FaceLandmarktypes re-exported from the top-level library. -
EkycImagePreprocessor— pure-Dart image preprocessing helpers:resize(defaults to maxLongEdge 1024),crop(withpaddingRatio, clamped to bounds),cropToFace(takes ML KitFace),reencodeJpeg. Synchronous internally but exposed asFutureso callers can swap tocompute()for off-main-thread without breaking the API. ThrowsArgumentErroron non-image input.
Changed
EkycBurstFaceVerifyWidgetnow delegates its internal burst loop toBurstFaceVerifier. 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@visibleForTestingnow used inlib/).
See Custom UI primitives for usage walkthroughs of each new API.
2.1.3 — 2026-05-13
Fixed
EkycLivenessWidgetnow 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'sCameraPreviewnatively on Android and iOS.
Added
CameraFacade.buildPreview()on the test-seam interface. ProductionDefaultCameraFacadereturns aCameraPreview; test fakes can returnSizedBox.shrink().LivenessController.cameragetter exposes the facade so integrators building custom liveness UIs can render the preview themselves.
2.1.2 — 2026-05-13
Fixed
EkycClient.faceVerifyImages,ocrScanImages, andlivenessCheckImagenow throw a typedEkycExceptiondirectly on 4xx / 5xx — matching the contract documented inErrorInterceptor("callers can useon EkycRateLimitException catch (e)"). Earlier 2.x versions leaked Dio's envelope: the typed exception was attached asDioException.errorand callers had to pattern-matchon DioException catch (e) { if (e.error is EkycException) }. v2.1.2 wraps each public boundary in_ekycCall<T>which strips theDioExceptionenvelope when the inner cause is anEkycException. Non-EkycExceptionDioExceptions (timeouts, DNS failures) still propagate asDioException.
2.1.1 — 2026-05-13
Fixed
EkycConfig.apiVersiondefault changed from'2026-05-12'to'2026-04-26'to match the gateway'sSUPPORTED_VERSIONS. The previous default was forward-dated from the 2.0.0 cut and causedunknown_version(HTTP 400) on every endpoint that goes through the gateway's version middleware — i.e. all of OCR, face, and liveness./v1/pingwas 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 theimage_a/image_bfields, so the codegen'dclient.face.faceVerify()sends no body. Use this helper.EkycClient.ocrScanImages({required File image, File? backImage})— same shape for/v1/ocr/scan. OptionalbackImagefor 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-flutter→DrukVerify/drukverify-flutter. The previous URL still redirects;pubspec.yamlmetadata (homepage,repository,issue_tracker) now points at the new canonical URL. - Brand-aligned typedef aliases added:
DrukVerifyClient,DrukVerifyConfig,DrukVerifyExceptionand subclasses all map 1:1 to theEkyc*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
-
Package rename:
# 1.xekyc_sdk: ^1.x# 2.0+drukverify_flutter:git:url: git@github.com:DrukVerify/drukverify-flutter.gitref: v2.2.0 -
EkycSdk→EkycClient;EkycConfig.baseUrl:→host:; the static-key constructor is replaced byapiKey:+tokenProvider::// 1.xfinal ekyc = EkycSdk(apiKey: 'static-key');// 2.0+final ekyc = EkycClient(config: EkycConfig(apiKey: 'pk_live_xxx', // public key, embeddable in apptokenProvider: () async { // your server mints session tokensfinal res = await http.post('https://your-server.com/ekyc/token');return jsonDecode(res.body)['token'];},)); -
Method renames:
face.verify(imageA:, imageB:)→faceVerifyImages(imageA:, imageB:)ocr.scan(image:, backImage:)→ocrScanImages(image:, backImage:)liveness.check(mode:, frames:)→livenessCheckImage(File)
-
LivenessModeenum is removed —EkycLivenessWidgetconfigures challenges viaLivenessConfig.allowedChallengesinstead. The widget'ssdk:prop is nowclient:, and themode:andonError:props are gone (errors arrive viaLivenessResult.errorinonResult). -
Result types are renamed to match the OpenAPI generator output:
FaceVerifyResult→FaceVerify200ResponseOcrResult→OcrList200ResponseDataInnerLivenessCheckResult→LivenessCheck200Response(raw) /LivenessResult(widget)
-
EkycError→EkycException;.code→.type;.paramfield removed.
Added
EkycClientfacade with multipart upload helpers + generated sub-API accessors (client.face,client.ocr,client.liveness,client.sessions,client.diagnostics)tokenProviderasync callback with single-flight reactive 401 refreshEkycLivenessWidgetwith 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-Idheaders
Removed
apiKey-only auth (replaced bytokenProviderflow)enableOnDeviceLivenesstoggle (on-device is always on, since it's coupled to challenge UX)- Hand-rolled HTTP client (replaced by
dio ^5.7) Fraud,Batch,Verificationnamespaces (deferred to SP5 / SP8)